Planteo del problema
En este caso vamos a tratar de resolver el problema de predecir si una persona que viajaba a bordo del Titanic sobrevivió o no. En particular, la regresión logística es útil para problemas de predicción de clases.
Queremos estimar \(P(Survived=1|X)=P(X)\) para cada individuo y a partir de ello poder definir un punto de corte para predecir quiénes son los que van a sobrevivir y los que no.
Para ello, utilizaremos el conjunto de datos que proviene de Kaggle - Titanic: Machine Learning from Disaster.
# Cargamos las librerías que vamos a utilizar
library(tidyverse)
library(tidymodels)
library(modelr)
library(GGally)
library(pROC)
library(cowplot)
library(OneR)
library(rlang)
library(caret)
set.seed(2021)
# cargamos los datasets de train y test
dftitanic_train <- read_csv("../Fuentes/titanic_complete_train.csv")
dftitanic_test <- read_csv("../Fuentes/titanic_complete_test.csv")
# observamos su estructura
glimpse(dftitanic_train)
Rows: 891
Columns: 12
$ PassengerId <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,~
$ Survived <dbl> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0,~
$ Pclass <dbl> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2,~
$ Name <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (Floren~
$ Sex <chr> "male", "female", "female", "female", "male", "male", "male", ~
$ Age <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50759, 54~
$ SibSp <dbl> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0,~
$ Parch <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0,~
$ Ticket <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "373450~
$ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21.~
$ Cabin <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, "G6", "C103"~
$ Embarked <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S", "S~
glimpse(dftitanic_test)
Rows: 418
Columns: 12
$ PassengerId <dbl> 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 90~
$ Pclass <dbl> 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 1, 1, 2, 1, 2, 2, 3, 3, 3, 1,~
$ Name <chr> "Kelly, Mr. James", "Wilkes, Mrs. James (Ellen Needs)", "Myles~
$ Sex <chr> "male", "female", "male", "male", "female", "male", "female", ~
$ Age <dbl> 34.50000, 47.00000, 62.00000, 27.00000, 22.00000, 14.00000, 30~
$ SibSp <dbl> 0, 1, 0, 0, 1, 0, 0, 1, 0, 2, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1,~
$ Parch <dbl> 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
$ Ticket <chr> "330911", "363272", "240276", "315154", "3101298", "7538", "33~
$ Fare <dbl> 7.8292, 7.0000, 9.6875, 8.6625, 12.2875, 9.2250, 7.6292, 29.00~
$ Cabin <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "B45", NA, "E3~
$ Embarked <chr> "Q", "S", "Q", "S", "S", "S", "Q", "S", "C", "S", "S", "S", "S~
$ Survived <dbl> 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0,~
El dataset de training tiene 12 variables y 891 observaciones, mientras que el de testing tiene 418.
Las variables del set incluyen id, nombre, edad, sexo del pasajero, como también:
- Survived: La clase que queremos predecir: Sobrevivió (0 = No, 1 = Yes).
- Pclass: La clase a la que pertenece el pasajero (1 = 1st, 2 = 2nd, 3 = 3rd). Es un proxy del estatus socio-económico (1st = Upper, 2nd = Middle, 3rd = Lower).
- Sibsp: número de hermanos / cónyuges a bordo del Titanic.
- Parch: número de padres / hijos a bordo del Titanic.
- Ticket: número de Ticket
- Fare: tarifa.
- Cabin: número de cabina
- Embarked: Puerto de embarque (C = Cherbourg, Q = Queenstown, S = Southampton).
Se transforman las variables Pclass y Embarked a factor.
dftitanic_train <- dftitanic_train %>%
mutate(Pclass = factor(Pclass), Embarked = factor(Embarked))
dftitanic_test <- dftitanic_test %>%
mutate(Pclass = factor(Pclass), Embarked = factor(Embarked))
Análisis Exploratorios
Analicemos la distribución de la clase en cada dataset.
# calculamos la distribución de clase en cada dataset
train <- dftitanic_train %>%
group_by(Survived) %>%
summarise(numero_casos=n()) %>%
mutate(prop = round(prop.table(numero_casos)*100,2))
test <- dftitanic_test %>%
group_by(Survived) %>%
summarise(numero_casos=n()) %>%
mutate(prop = round(prop.table(numero_casos)*100,2))
# armamos tabla conjunta para graficar
distrib = cbind(rbind(train, test), dataset = c("train", "train", "test", "test"))
distrib
# graficamos las distribuciones
ggplot(distrib, aes(x = Survived, y = prop, fill = factor(Survived), label = prop)) +
geom_bar(stat="identity", position = "dodge") + facet_wrap(~ dataset) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
labs(x = "Sobrevivientes", y = "Proporción en %", title = "Proporción de sobrevivientes por dataset") +
theme_bw() +
scale_fill_brewer(palette="Set1")

Vemos que estamos trabajando con un problema de clasificación con cierto desbalance de clase, pero que la proporción se mantiene en ambos conjuntos de datos.
Realizamos un gráfico exploratorio completo para ver el comportamiento y las relaciones entre las variables. El color rojo designa a quienes no sobrevivieron y el azul a los que sí.
# graficamos con ggpairs coloreando por variable a predecir
g <- dftitanic_train %>%
select("Survived","Pclass", "Sex", "Age", "Fare", "SibSp", "Parch") %>%
ggpairs(title = "Correlograma de variables",
mapping = aes(colour= factor(Survived)),
progress = FALSE,
lower=list(combo=wrap("facethist", binwidth=0.8))) +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
theme_bw() +
scale_fill_brewer(palette="Set1") +
scale_color_brewer(palette="Set1")
g

¿Qué pueden decir de la relación entre clase (Pclass) y supervivencia (Survived)?
¿Y entre edad (Age) y supervivencia?
¿Cuáles parecen ser buenas variables para discriminar entre quienes sobrevivieron y quienes no?
Probemos algunas soluciones
Regresión lineal
En este caso estamos modelando la probabilidad de la siguiente manera:
\(P(X)= \beta_0 + \sum\limits_{j=1}^p \beta_j X\)
Veamos que tan bueno es el modelo lineal para esto, usando la edad como predictor.
mrl <- dftitanic_train %>%
lm(formula = Survived ~ Age)
tdy = mrl %>% tidy()
tdy
mrl %>% glance()
Los estimadores son significativos y el test de significatividad global del modelo también es significativo.
Veamos un gráfico de nuestro modelo.

Parece tener bastantes problemas para estimar la probabilidad de supervivencia de los individuo: no existe un punto de corte claro, la predicción podría ser mayor a 1 o menor a cero llegado el caso.
Regresión Logística
Para evitar estos problemas, usamos la función logística.
\(P(Y=1|X)= \frac{e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}{1+e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}\)
El lado derecho se llama expit
Esta función acota el resultado entre 0 y 1, lo cual es mucho más adecuado para modelar una probabilidad.
Luego de hacer algunas operaciones, podemos llegar a la expresión:
\(\log {\frac{P(x)}{1-P(x)}}= \beta_0 + \sum\limits_{j=1}^p \beta_j X\)
El lado izquierdo es el logaritmo de los odds y se llama logit.
Modelo
La funcíón glm() nos permite crear un modelo lineal generalizado (Generalized Linear Model). Al igual que la función lm() toma como argumentos una formula y los datos pero también se debe especificar el argumento family: indicamos la distribución del error y la función link que vamos a utilizar en el modelo.
Algunas familias son:
Como estamos trabajando con un fenómeno que suponemos tiene una distribución binomial, así lo especificamos en el parámetro family.
Realizamos un modelo de regresión logística para predecir la supervivencia en función de Pclass, Sex y Age.
# modelo de regresión logística
glm1 <- glm(data = dftitanic_train, Survived ~ Pclass + Sex + Age, family = 'binomial')
# veo los resultados
tidy(glm1)
glance(glm1)
Se va a discutir sobre la interpretación de coeficientes y la evaluación en las siguientes secciones.
Creación de fórmulas
Para crear varios modoelos de regresión logística podemos utilizar la función formulas del paquete modelr para crear un objeto que contiene todas las fórmulas que vamos a utilizar.
En .response especificamos la variable respuesta de nuestras fórmulas y luego nombramos las fórmulas que queramos armar.
Así, armaremos distintos modelos combinando distintas variables. Se generan 7 modelos distintos para predecir la supervivencia en función de distintas combinaciones de las variables pclass, sex, age y fare.
# Creación de fórmulas
logit_formulas <- formulas(.response = ~ Survived,
class = ~ Pclass,
sex = ~ Sex,
age = ~ Age,
PcS = ~ Pclass + Sex, # modelo con las variables que más parecen dividir el target
PcSA = ~ Pclass + Sex + Age, # modelo glm1
PcSF = ~ Pclass + Sex + Fare, # modelo previo sin Age
PcSAF = ~ Pclass + Sex + Age + Fare # modelo glm1 con Fare
)
logit_formulas # observamos el objeto formulas
$class
Survived ~ Pclass
$sex
Survived ~ Sex
$age
Survived ~ Age
$PcS
Survived ~ Pclass + Sex
$PcSA
Survived ~ Pclass + Sex + Age
$PcSF
Survived ~ Pclass + Sex + Fare
$PcSAF
Survived ~ Pclass + Sex + Age + Fare
Creación de modelos
Procedemos a crear los modelos a partir de estas fórmulas.
models <- data_frame(logit_formulas) %>% # dataframe a partir del objeto formulas
mutate(models = names(logit_formulas), # columna con los nombres de las formulas
expression = paste(logit_formulas), # columna con las expresiones de las formulas
mod = map(logit_formulas, ~glm(., family = 'binomial', data = dftitanic_train))) # Que estamos haciendo acá? Que vamos a encontrar en la columna?
models
Modelos simples
Probamos los primeros tres modelos, aquellos que tienen un único predictor. Usamos la función tidy para obtener los parámetros estimados para estos tres modelos.
models %>%
filter(models %in% c('class','sex','age')) %>%
mutate(tidy = map(mod, tidy)) # Qué realizamos en este paso? Que va a tener esta columna?
Para acceder a los elementos de la nueva columna tidy debemos desanidarla (usando unnest()). El anidado crea una columna de listas de dataframes, es implícitamente una operación de resumen: obtiene una fila para cada grupo definido por las columnas no anidadas. Desanidar lo aplana de nuevo en columnas regulares. Para mayor detalle, sugerimos ver la documentación de tidyr sobre Nest and unnest.
models %>%
filter(models %in% c('class','sex','age')) %>%
mutate(tidy = map(mod, tidy)) %>% # Qué realizamos en este paso? Que va a tener esta columna?
unnest(tidy) %>%
mutate(estimate=round(estimate,5), # redondeamos valores para facilitar lectura
p.value=round(p.value,4))
Observamos que todos los modelos tienen coeficientes significativos aunque el de Age se encuentra muy cercano a nuestro valor de rechazo.
Evaluación de todos los modelos
Con map() agregamos la función glance para traer información relevante para la evaluación del modelo. Con unnest() accedemos a dicha información. Por último, agregamos una columna con el porcentaje de deviance explicado por cada modelo y ordenamos el dataset según su valor de deviance.
# Calcular las medidas de evaluación para cada modelo
models <- models %>%
mutate(glance = map(mod,glance))
# Obtener las medidas de evaluacion de interes
models %>%
unnest(glance) %>%
# Calculamos la deviance explicada
mutate(perc_explained_dev = 1-deviance/null.deviance) %>%
select(-c(models, df.null, AIC, BIC)) %>%
arrange(deviance)
Los modelos que incluyen las 3 variables Pclass, Sex y Age parecen ser los que minimizan la deviance. Observamos también que los 2 últimos modelos reducen muy poco la deviance respecto a la deviance nula.
Gráficos de Evaluación
Realizamos los gráficos para el modelo completo y uno de los modelos con mayor deviance (Age).
Comenzamos agregando las predicciones con augment con el parámetro type="response". La función augment hereda el argumento type.predict de la función predict.
Si type.predict = 'link' la predicción es en términos de la función link. En nuestro caso son el logaritmo de las odds, es decir, los valores que toma la expresión logit.
Si type.predict = 'response' la predicción son las probabilidades de que la observación pertenezca a la clase positiva. En nuestro caso, devuelve la probabilidad de la que persona sobreviva.
# Añadir las predicciones
models <- models %>%
mutate(pred= map(mod, augment, type.predict = "response"))
#Observaciones con probabilidad más baja
models$pred$PcSAF %>% arrange(.fitted) %>% head(10)
#Observaciones con probabilidad más alta
models$pred$PcSAF %>% arrange(desc(.fitted)) %>% head(10)
Guardamos las predicciones para los modelos mencionados.
# Modelo completo
prediction_full <- models %>%
filter(models=="PcSAF") %>%
unnest(pred)
#Modelo malo
prediction_bad <- models %>%
filter(models=="age") %>%
unnest(pred)
Violin plots
# graficamos el modelo completo
violin_full = ggplot(prediction_full, aes(x=Survived, y=.fitted, group=Survived, fill=factor(Survived))) +
geom_violin() +
theme_bw() +
guides(scale="none") +
labs(title='Violin plot', subtitle='Modelo completo', y='Predicted probability')
# graficamos el modelo malo
violin_bad = ggplot(prediction_bad, aes(x=Survived, y=.fitted, group=Survived, fill=factor(Survived))) +
geom_violin() +
theme_bw() +
guides(scale="none") +
labs(title='Violin plot', subtitle='Modelo malo', y='Predicted probability')
# mostramos ambos
plot_grid(violin_bad, violin_full)

En los gráficos de violin observamos:
En el eje de abscisas la clase verdadera: Survived o No Survived (1 o 0).
En el eje de ordenadas la probabilidad predicha por nuestro modelo.
El gráfico nos muestra la distribución de la cantidad de observaciones por su clase real y la probabilidad que le asigna nuestro modelo.
¿Cuál parece ser un punto de corte adecuado para cada modelo?
Gráfico de Hosmer-Lemeshow
Se genera una función para realizar un gráfico de Hosmer-Lemeshow para un dataset. Para ello se fijan los siguientes parámetros:
dataset: conjunto de datos
predicted_column: columna con la probabilidad predicha
class_column: columna con la clase a predecir
possitive_value: valor de la clase a predecir
bins: cantidad de grupos del gráfico
color: color de los puntos
nudge_x: desplazamiento de la etiqueta en el eje x
nudge_y: desplazamiento de la etiqueta en el eje y
Hosmer_Lemeshow_plot <- function(dataset, predicted_column, class_column, bins, positive_value, color='forestgreen', nudge_x=0, nudge_y=0.05){
# Asignar los grupos a las observaciones de acuerdo a la probabilidad predicha
dataset['group'] <- bin(dataset[predicted_column], nbins = bins, method = 'l', labels=c(1:bins))
# Contar la cantidad de casos positivos por grupo
positive_class <- dataset %>% filter(!!sym(class_column)==positive_value) %>% group_by(group) %>% count()
# Obtener la media de las predicciones por grupo
HL_df <- dataset %>% group_by(group) %>% summarise(pred=mean(!!sym(predicted_column)), count=n()) %>%
inner_join(.,positive_class) %>%
mutate(freq=n/count)
# Gráfico
HM_plot <- ggplot(HL_df, aes(x=pred, y=freq)) +
geom_point(aes(size=n), color=color) +
geom_text(aes(label=n),nudge_y = nudge_y)+
geom_abline(slope = 1, intercept = 0, linetype='dashed') +
theme_bw() +
labs(title='Hosmer-Lemeshow', size='Casos', x="Probabilidad Predicha", y="Frecuencia observada")
return(HM_plot)
}
Generamos los gráficos pasandole lo parámetros.
# modelo completo
Hosmer_Lemeshow_plot(prediction_full, '.fitted', 'Survived', 10, 1) +
labs(subtitle="Modelo completo")
Joining, by = "group"

# modelo malo
Hosmer_Lemeshow_plot(prediction_bad, '.fitted', 'Survived', 10, 1, color = "firebrick") + labs(subtitle="Modelo malo")
Joining, by = "group"

En los gráficos de Hosmer-Lemeshow observamos:
En el eje de abscisas la probabilidad predicha de supervivencia.
En el eje de ordenadas la frecuencia de clase, el cociente entre cantidad de individuos Survived y el total de individuos.
La línea punteada designa la igualdad entre probabilidad predicha y frecuencia de clase.
Los círculos, que se construyen de la siguiente manera:
- Se dividen a las observaciones en bins en base a la probabilidad predicha
- Se calcula la frecuencia de clase para cada bin
- En base a estas dos coordenadas se ubica al círculo en el gráfico
- El número y tamaño indican la cantidad de observaciones en dicho grupo
Aquellos círculos que se ubiquen por encima de la línea punteada indican que el modelo está subestimando la probabilidad para dichos grupos. Mientras que si los círculos se ubican por debajo el modelo está sobreestimando la probabilidad para dichos grupos.
¿Para qué valores parece existir una sobreestimación de la probabilidad? ¿Para cuáles subestimación?
Curvas ROC
# Calculamos curvas ROC
roc_full <- roc(response=prediction_full$Survived, predictor=prediction_full$.fitted)
roc_bad <- roc(response=prediction_bad$Survived, predictor=prediction_bad$.fitted)
Graficamos ambas en un mismo plot.
ggroc(list(full=roc_full, bad=roc_bad), size=1) +
geom_abline(slope = 1, intercept = 1, linetype='dashed') +
theme_bw() +
labs(title='Curvas ROC', color='Modelo')

print(paste('AUC: Modelo completo', round(roc_full$auc,3)))
[1] "AUC: Modelo completo 0.848"
print(paste('AUC: Modelo malo', round(roc_bad$auc,3)))
[1] "AUC: Modelo malo 0.474"
¿Qué significa cada uno de los ejes?
Punto de corte
Hasta ahora hemos evaluado el modelo de manera general, pero el resultado final del modelo debe consistir en asignar a la persona una clase predicha. En nuestro caso debemos establecer un punto de corte según el cual vamos a separar a las personas en quienes sobreviven y quienes no.
Probamos varios puntos de corte y graficamos el accuracy, la sensibilidad o recall, la especificidad y la precisión para cada uno de ellos.
| Negativa |
True Neg |
False Neg |
| Positiva |
False Pos |
True Pos |
Recordemos que:
\(accuracy = \frac{TP+TN}{TP+FP+FN+TN}\)
\(sensitivity = recall = \frac{TP}{TP+FN}\)
\(specificity = \frac{TN}{TN+FP}\)
\(precision = \frac{TP}{TP+FP}\)
prediction_metrics <- function(cutoff, predictions=prediction_full){
tab <- predictions %>%
mutate(predicted_class = if_else(.fitted > cutoff, 1, 0),
Survived = factor(Survived))
confusionMatrix(table(tab$predicted_class, tab$Survived), positive = "1") %>%
tidy() %>%
select(term, estimate) %>%
filter(term %in% c('accuracy', 'sensitivity', 'specificity', 'precision')) %>%
mutate(cutoff = cutoff)
}
cutoffs = seq(0.05,0.95,0.01)
logit_pred = map_df(cutoffs, prediction_metrics) %>%
mutate(term = as.factor(term), estimate = round(estimate, 3))
ggplot(logit_pred, aes(cutoff,estimate, group=term, color=term)) + geom_line(size=1) +
theme_bw() +
labs(title= 'Accuracy, Sensitivity, Specificity y Precision', subtitle= 'Modelo completo', color="")

¿Qué podemos observar en el gráfico?
¿Podemos definir un buen punto de corte? ¿Cuál sería?
¿Por qué la especificidad tiene ese comportamiento?
Dataset de testing
Seleccionamos el modelo completo, ya que es el que maximizaba el porcentaje de deviance explicada y en base a lo que vimos definimos un punto de corte en 0.4 (pueden probar otros), donde se cruzan sensitivity y specificity.
Calculamos la matriz de confusión para los datasets de train y test.
# Creamos la matriz de confusión
confusionMatrix(table(table_train$predicted_class, table_train$Survived), positive = "1")
Confusion Matrix and Statistics
0 1
0 427 68
1 122 274
Accuracy : 0.7868
95% CI : (0.7584, 0.8132)
No Information Rate : 0.6162
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.5622
Mcnemar's Test P-Value : 0.0001205
Sensitivity : 0.8012
Specificity : 0.7778
Pos Pred Value : 0.6919
Neg Pred Value : 0.8626
Prevalence : 0.3838
Detection Rate : 0.3075
Detection Prevalence : 0.4444
Balanced Accuracy : 0.7895
'Positive' Class : 1
# Agregamos la predicciones al dataset de testeo
table_test = augment(x = full_model, newdata=dftitanic_test, type.predict='response')
# Clasificamos utilizamos el punto de corte
table_test = table_test %>%
mutate(predicted_class = if_else(.fitted>sel_cutoff, 1, 0) %>% as.factor(),
Survived = factor(Survived))
# Creamos la matriz de confusión
confusionMatrix(table(table_test$predicted_class, table_test$Survived), positive = "1")
Confusion Matrix and Statistics
0 1
0 191 39
1 70 118
Accuracy : 0.7392
95% CI : (0.6943, 0.7807)
No Information Rate : 0.6244
P-Value [Acc > NIR] : 4.341e-07
Kappa : 0.4651
Mcnemar's Test P-Value : 0.00406
Sensitivity : 0.7516
Specificity : 0.7318
Pos Pred Value : 0.6277
Neg Pred Value : 0.8304
Prevalence : 0.3756
Detection Rate : 0.2823
Detection Prevalence : 0.4498
Balanced Accuracy : 0.7417
'Positive' Class : 1
Al existir desbalanceo de clases, es posible que el analista desee saber si la precisión general (Accuracy) de su modelo es mejor que la proporción de datos con la clase mayoritaria (No-information Rate). confusionMatrix usa la función binom.test para probar que la precisión (Acc) es mejor que la tasa sin información (NIR). Si el P-Value [Acc > NIR] resulta significativo, entonces podemos decir que existe evidencia estadísticamente significativa para decir que la precisión del modelo es mejor que la tasa sin información.
LS0tDQp0aXRsZTogIlJlZ3Jlc2nDs24gTG9nw61zdGljYSINCmF1dGhvcjogIkp1YW4gQmFycmlvbGEgeSBTb2bDrWEgUGVyaW5pIg0KZGF0ZTogIjMwIGRlIE9jdHVicmUgZGUgMjAyMSINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQpkaXYubWFpbi1jb250YWluZXIgew0KICBtYXgtd2lkdGg6IDE2MDBweDsNCiAgbWFyZ2luLWxlZnQ6IGF1dG87DQogIG1hcmdpbi1yaWdodDogYXV0bzsNCn0NCjwvc3R5bGU+DQoNCiMjIFBsYW50ZW8gZGVsIHByb2JsZW1hDQoNCkVuIGVzdGUgY2FzbyB2YW1vcyBhIHRyYXRhciBkZSByZXNvbHZlciBlbCBwcm9ibGVtYSBkZSAqKnByZWRlY2lyIHNpIHVuYSBwZXJzb25hIHF1ZSB2aWFqYWJhIGEgYm9yZG8gZGVsIFRpdGFuaWMgc29icmV2aXZpw7MgbyBubyoqLiBFbiBwYXJ0aWN1bGFyLCBsYSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgZXMgw7p0aWwgcGFyYSBwcm9ibGVtYXMgZGUgcHJlZGljY2nDs24gZGUgY2xhc2VzLg0KDQpRdWVyZW1vcyBlc3RpbWFyICRQKFN1cnZpdmVkPTF8WCk9UChYKSQgcGFyYSBjYWRhIGluZGl2aWR1byB5IGEgcGFydGlyIGRlIGVsbG8gcG9kZXIgZGVmaW5pciB1biBwdW50byBkZSBjb3J0ZSBwYXJhIHByZWRlY2lyIHF1acOpbmVzIHNvbiBsb3MgcXVlIHZhbiBhIHNvYnJldml2aXIgeSBsb3MgcXVlIG5vLg0KDQpQYXJhIGVsbG8sIHV0aWxpemFyZW1vcyBlbCBjb25qdW50byBkZSBkYXRvcyBxdWUgcHJvdmllbmUgZGUgW0thZ2dsZSAtIFRpdGFuaWM6IE1hY2hpbmUgTGVhcm5pbmcgZnJvbSBEaXNhc3Rlcl0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jL3RpdGFuaWMvb3ZlcnZpZXcpLg0KDQpgYGB7ciwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBDYXJnYW1vcyBsYXMgbGlicmVyw61hcyBxdWUgdmFtb3MgYSB1dGlsaXphcg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KG1vZGVscikNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShjb3dwbG90KQ0KbGlicmFyeShPbmVSKQ0KbGlicmFyeShybGFuZykNCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgyMDIxKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBjYXJnYW1vcyBsb3MgZGF0YXNldHMgZGUgdHJhaW4geSB0ZXN0IA0KZGZ0aXRhbmljX3RyYWluIDwtIHJlYWRfY3N2KCIuLi9GdWVudGVzL3RpdGFuaWNfY29tcGxldGVfdHJhaW4uY3N2IikNCmRmdGl0YW5pY190ZXN0IDwtIHJlYWRfY3N2KCIuLi9GdWVudGVzL3RpdGFuaWNfY29tcGxldGVfdGVzdC5jc3YiKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KIyBvYnNlcnZhbW9zIHN1IGVzdHJ1Y3R1cmENCmdsaW1wc2UoZGZ0aXRhbmljX3RyYWluKQ0KZ2xpbXBzZShkZnRpdGFuaWNfdGVzdCkNCmBgYA0KDQpFbCBkYXRhc2V0IGRlIHRyYWluaW5nIHRpZW5lIDEyIHZhcmlhYmxlcyB5IDg5MSBvYnNlcnZhY2lvbmVzLCBtaWVudHJhcyBxdWUgZWwgZGUgdGVzdGluZyB0aWVuZSA0MTguDQoNCkxhcyB2YXJpYWJsZXMgZGVsIHNldCBpbmNsdXllbiBpZCwgbm9tYnJlLCBlZGFkLCBzZXhvIGRlbCBwYXNhamVybywgY29tbyB0YW1iacOpbjoNCg0KKiAqKlN1cnZpdmVkKio6IExhIGNsYXNlIHF1ZSBxdWVyZW1vcyBwcmVkZWNpcjogU29icmV2aXZpw7MgKDAgPSBObywgMSA9IFllcykuDQoqICoqUGNsYXNzKio6IExhIGNsYXNlIGEgbGEgcXVlIHBlcnRlbmVjZSBlbCBwYXNhamVybyAoMSA9IDFzdCwgMiA9IDJuZCwgMyA9IDNyZCkuIEVzIHVuIHByb3h5IGRlbCBlc3RhdHVzIHNvY2lvLWVjb27Ds21pY28gKDFzdCA9IFVwcGVyLCAybmQgPSBNaWRkbGUsIDNyZCA9IExvd2VyKS4gDQoqICoqU2lic3AqKjogbsO6bWVybyBkZSBoZXJtYW5vcyAvIGPDs255dWdlcyBhIGJvcmRvIGRlbCBUaXRhbmljLg0KKiAqKlBhcmNoKio6IG7Dum1lcm8gZGUgcGFkcmVzIC8gaGlqb3MgYSBib3JkbyBkZWwgVGl0YW5pYy4JDQoqICoqVGlja2V0Kio6IG7Dum1lcm8gZGUJVGlja2V0DQoqICoqRmFyZSoqOiB0YXJpZmEuIA0KKiAqKkNhYmluKio6IG7Dum1lcm8gZGUgY2FiaW5hDQoqICoqRW1iYXJrZWQqKjogUHVlcnRvIGRlIGVtYmFycXVlIChDID0gQ2hlcmJvdXJnLCBRID0gUXVlZW5zdG93biwgUyA9IFNvdXRoYW1wdG9uKS4gDQoNClNlIHRyYW5zZm9ybWFuIGxhcyB2YXJpYWJsZXMgKipQY2xhc3MqKiB5ICoqRW1iYXJrZWQqKiBhIGZhY3Rvci4NCg0KYGBge3J9DQpkZnRpdGFuaWNfdHJhaW4gPC0gZGZ0aXRhbmljX3RyYWluICU+JQ0KICBtdXRhdGUoUGNsYXNzID0gZmFjdG9yKFBjbGFzcyksIEVtYmFya2VkID0gZmFjdG9yKEVtYmFya2VkKSkNCmRmdGl0YW5pY190ZXN0IDwtIGRmdGl0YW5pY190ZXN0ICU+JQ0KICBtdXRhdGUoUGNsYXNzID0gZmFjdG9yKFBjbGFzcyksIEVtYmFya2VkID0gZmFjdG9yKEVtYmFya2VkKSkNCmBgYA0KDQojIyBBbsOhbGlzaXMgRXhwbG9yYXRvcmlvcw0KDQpBbmFsaWNlbW9zIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGEgY2xhc2UgZW4gY2FkYSBkYXRhc2V0LiANCg0KYGBge3J9DQojIGNhbGN1bGFtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBjbGFzZSBlbiBjYWRhIGRhdGFzZXQNCnRyYWluIDwtIGRmdGl0YW5pY190cmFpbiAlPiUgDQogIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUgDQogIHN1bW1hcmlzZShudW1lcm9fY2Fzb3M9bigpKSAlPiUNCiAgbXV0YXRlKHByb3AgPSByb3VuZChwcm9wLnRhYmxlKG51bWVyb19jYXNvcykqMTAwLDIpKQ0KdGVzdCA8LSBkZnRpdGFuaWNfdGVzdCAlPiUgDQogIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUgDQogIHN1bW1hcmlzZShudW1lcm9fY2Fzb3M9bigpKSAlPiUNCiAgbXV0YXRlKHByb3AgPSByb3VuZChwcm9wLnRhYmxlKG51bWVyb19jYXNvcykqMTAwLDIpKQ0KIyBhcm1hbW9zIHRhYmxhIGNvbmp1bnRhIHBhcmEgZ3JhZmljYXINCmRpc3RyaWIgPSBjYmluZChyYmluZCh0cmFpbiwgdGVzdCksIGRhdGFzZXQgPSBjKCJ0cmFpbiIsICJ0cmFpbiIsICJ0ZXN0IiwgInRlc3QiKSkNCmRpc3RyaWINCiMgZ3JhZmljYW1vcyBsYXMgZGlzdHJpYnVjaW9uZXMNCmdncGxvdChkaXN0cmliLCBhZXMoeCA9IFN1cnZpdmVkLCB5ID0gcHJvcCwgZmlsbCA9IGZhY3RvcihTdXJ2aXZlZCksIGxhYmVsID0gcHJvcCkpICsgDQogICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKyBmYWNldF93cmFwKH4gZGF0YXNldCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArDQogIGxhYnMoeCA9ICJTb2JyZXZpdmllbnRlcyIsIHkgPSAiUHJvcG9yY2nDs24gZW4gJSIsIHRpdGxlID0gIlByb3BvcmNpw7NuIGRlIHNvYnJldml2aWVudGVzIHBvciBkYXRhc2V0IikgKyANCiAgdGhlbWVfYncoKSArDQogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKQ0KYGBgDQoNClZlbW9zIHF1ZSBlc3RhbW9zIHRyYWJhamFuZG8gY29uIHVuIHByb2JsZW1hIGRlIGNsYXNpZmljYWNpw7NuIGNvbiBjaWVydG8gZGVzYmFsYW5jZSBkZSBjbGFzZSwgcGVybyBxdWUgbGEgcHJvcG9yY2nDs24gc2UgbWFudGllbmUgZW4gYW1ib3MgY29uanVudG9zIGRlIGRhdG9zLiANCg0KUmVhbGl6YW1vcyB1biBncsOhZmljbyBleHBsb3JhdG9yaW8gY29tcGxldG8gcGFyYSB2ZXIgZWwgY29tcG9ydGFtaWVudG8geSBsYXMgcmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzLiBFbCBjb2xvciByb2pvIGRlc2lnbmEgYSBxdWllbmVzIG5vIHNvYnJldml2aWVyb24geSBlbCBhenVsIGEgbG9zIHF1ZSBzw60uDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02LCBwcm9ncmVzcz1GQUxTRX0NCiMgZ3JhZmljYW1vcyBjb24gZ2dwYWlycyBjb2xvcmVhbmRvIHBvciB2YXJpYWJsZSBhIHByZWRlY2lyDQpnIDwtIGRmdGl0YW5pY190cmFpbiAlPiUgDQogICAgICAgIHNlbGVjdCgiU3Vydml2ZWQiLCJQY2xhc3MiLCAiU2V4IiwgIkFnZSIsICJGYXJlIiwgIlNpYlNwIiwgIlBhcmNoIikgJT4lIA0KICAgICAgICBnZ3BhaXJzKHRpdGxlID0gIkNvcnJlbG9ncmFtYSBkZSB2YXJpYWJsZXMiLA0KICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoY29sb3VyPSBmYWN0b3IoU3Vydml2ZWQpKSwNCiAgICAgICAgICAgICAgICBwcm9ncmVzcyA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICBsb3dlcj1saXN0KGNvbWJvPXdyYXAoImZhY2V0aGlzdCIsIGJpbndpZHRoPTAuOCkpKSArDQogICAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICsgDQogICAgICAgIHRoZW1lX2J3KCkgKw0KICAgICAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKw0KICAgICAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpDQpnDQpgYGANCg0Kwr9RdcOpIHB1ZWRlbiBkZWNpciBkZSBsYSByZWxhY2nDs24gZW50cmUgY2xhc2UgKFBjbGFzcykgeSBzdXBlcnZpdmVuY2lhIChTdXJ2aXZlZCk/DQoNCsK/WSBlbnRyZSBlZGFkIChBZ2UpIHkgc3VwZXJ2aXZlbmNpYT8NCg0Kwr9DdcOhbGVzIHBhcmVjZW4gc2VyIGJ1ZW5hcyB2YXJpYWJsZXMgcGFyYSBkaXNjcmltaW5hciBlbnRyZSBxdWllbmVzIHNvYnJldml2aWVyb24geSBxdWllbmVzIG5vPw0KDQojIyMgUHJvYmVtb3MgYWxndW5hcyBzb2x1Y2lvbmVzDQoNCiMjIyBSZWdyZXNpw7NuIGxpbmVhbA0KDQpFbiBlc3RlIGNhc28gZXN0YW1vcyBtb2RlbGFuZG8gbGEgcHJvYmFiaWxpZGFkIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6IA0KDQokUChYKT0gXGJldGFfMCArIFxzdW1cbGltaXRzX3tqPTF9XnAgXGJldGFfaiBYJA0KDQpWZWFtb3MgcXVlIHRhbiBidWVubyBlcyBlbCBtb2RlbG8gbGluZWFsIHBhcmEgZXN0bywgdXNhbmRvIGxhIGVkYWQgY29tbyBwcmVkaWN0b3IuDQoNCmBgYHtyfQ0KbXJsIDwtIGRmdGl0YW5pY190cmFpbiAlPiUgDQogICAgICAgICAgICAgIGxtKGZvcm11bGEgPSBTdXJ2aXZlZCB+IEFnZSkgDQp0ZHkgPSBtcmwgJT4lIHRpZHkoKSANCnRkeQ0KbXJsICU+JSBnbGFuY2UoKQ0KYGBgDQoNCkxvcyBlc3RpbWFkb3JlcyBzb24gc2lnbmlmaWNhdGl2b3MgeSBlbCB0ZXN0IGRlIHNpZ25pZmljYXRpdmlkYWQgZ2xvYmFsIGRlbCBtb2RlbG8gdGFtYmnDqW4gZXMgc2lnbmlmaWNhdGl2by4NCg0KVmVhbW9zIHVuIGdyw6FmaWNvIGRlIG51ZXN0cm8gbW9kZWxvLg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmdncGxvdChkZnRpdGFuaWNfdHJhaW4sIGFlcyhBZ2UsIFN1cnZpdmVkKSkgKyANCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9ZmFjdG9yKFN1cnZpdmVkKSkpICsNCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsgDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IHRkeSRlc3RpbWF0ZVsxXSwgc2xvcGUgPSB0ZHkkZXN0aW1hdGVbMl0sIGNvbG9yPSdmb3Jlc3RncmVlbicsIHNpemU9MikgKyANCiAgbGFicyh0aXRsZT0iTW9kZWxvIExpbmVhbCBTaW1wbGUiLCBjb2xvcj0nQ2xhc2UnKSArDQogIGxpbXMoeT1jKC0xLDIpKSsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNClBhcmVjZSB0ZW5lciBiYXN0YW50ZXMgcHJvYmxlbWFzIHBhcmEgZXN0aW1hciBsYSBwcm9iYWJpbGlkYWQgZGUgc3VwZXJ2aXZlbmNpYSBkZSBsb3MgaW5kaXZpZHVvOiBubyBleGlzdGUgdW4gcHVudG8gZGUgY29ydGUgY2xhcm8sIGxhIHByZWRpY2Npw7NuIHBvZHLDrWEgc2VyIG1heW9yIGEgMSBvIG1lbm9yIGEgY2VybyBsbGVnYWRvIGVsIGNhc28uDQoNCiMjIFJlZ3Jlc2nDs24gTG9nw61zdGljYQ0KDQpQYXJhIGV2aXRhciBlc3RvcyBwcm9ibGVtYXMsIHVzYW1vcyBsYSAqKmZ1bmNpw7NuIGxvZ8Otc3RpY2EqKi4NCg0KJFAoWT0xfFgpPSBcZnJhY3tlXntcYmV0YV8wICsgXHN1bVxsaW1pdHNfe2o9MX1ecCBcYmV0YV9qIFh9fXsxK2Vee1xiZXRhXzAgKyBcc3VtXGxpbWl0c197aj0xfV5wIFxiZXRhX2ogWH19JA0KDQpFbCBsYWRvIGRlcmVjaG8gc2UgbGxhbWEgKipleHBpdCoqDQoNCkVzdGEgZnVuY2nDs24gYWNvdGEgZWwgcmVzdWx0YWRvIGVudHJlIDAgeSAxLCBsbyBjdWFsIGVzIG11Y2hvIG3DoXMgYWRlY3VhZG8gcGFyYSBtb2RlbGFyIHVuYSBwcm9iYWJpbGlkYWQuDQoNCkx1ZWdvIGRlIGhhY2VyIGFsZ3VuYXMgb3BlcmFjaW9uZXMsIHBvZGVtb3MgbGxlZ2FyIGEgbGEgZXhwcmVzacOzbjoNCg0KJFxsb2cge1xmcmFje1AoeCl9ezEtUCh4KX19PSBcYmV0YV8wICsgXHN1bVxsaW1pdHNfe2o9MX1ecCBcYmV0YV9qIFgkDQoNCkVsIGxhZG8gaXpxdWllcmRvIGVzIGVsIGxvZ2FyaXRtbyBkZSBsb3MgKipvZGRzKiogeSBzZSBsbGFtYSAqKmxvZ2l0KiouDQoNCiMjIyBNb2RlbG8gDQoNCkxhIGZ1bmPDrcOzbiBgZ2xtKClgIG5vcyBwZXJtaXRlIGNyZWFyIHVuIG1vZGVsbyBsaW5lYWwgZ2VuZXJhbGl6YWRvIChHZW5lcmFsaXplZCBMaW5lYXIgTW9kZWwpLiBBbCBpZ3VhbCBxdWUgbGEgZnVuY2nDs24gYGxtKClgIHRvbWEgY29tbyBhcmd1bWVudG9zIHVuYSAqKmZvcm11bGEqKiB5IGxvcyAqKmRhdG9zKiogcGVybyB0YW1iacOpbiBzZSBkZWJlIGVzcGVjaWZpY2FyIGVsIGFyZ3VtZW50byAqKmZhbWlseSoqOiBpbmRpY2Ftb3MgbGEgZGlzdHJpYnVjacOzbiBkZWwgZXJyb3IgeSBsYSBmdW5jacOzbiBsaW5rIHF1ZSB2YW1vcyBhIHV0aWxpemFyIGVuIGVsIG1vZGVsby4gDQoNCkFsZ3VuYXMgZmFtaWxpYXMgc29uOg0KDQoqICpCaW5vbWlhbCo6IGxpbms9bG9naXQNCg0KKiAqUG9pc3Nvbio6IGxpbms9bG9nDQoNCiogKkdhdXNzaWFuYSo6IGxpbms9aWRlbnRpZGFkDQoNCkNvbW8gZXN0YW1vcyB0cmFiYWphbmRvIGNvbiB1biBmZW7Ds21lbm8gcXVlIHN1cG9uZW1vcyB0aWVuZSB1bmEgZGlzdHJpYnVjacOzbiBiaW5vbWlhbCwgYXPDrSBsbyBlc3BlY2lmaWNhbW9zIGVuIGVsIHBhcsOhbWV0cm8gKipmYW1pbHkqKi4NCg0KUmVhbGl6YW1vcyB1biBtb2RlbG8gZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhIHBhcmEgcHJlZGVjaXIgbGEgc3VwZXJ2aXZlbmNpYSBlbiBmdW5jacOzbiBkZSAqKlBjbGFzcyoqLCAqKlNleCoqIHkgKipBZ2UqKi4gDQoNCmBgYHtyfQ0KIyBtb2RlbG8gZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhIA0KZ2xtMSA8LSBnbG0oZGF0YSA9IGRmdGl0YW5pY190cmFpbiwgU3Vydml2ZWQgfiBQY2xhc3MgKyBTZXggKyBBZ2UsIGZhbWlseSA9ICdiaW5vbWlhbCcpDQojIHZlbyBsb3MgcmVzdWx0YWRvcw0KdGlkeShnbG0xKQ0KZ2xhbmNlKGdsbTEpDQpgYGANCg0KU2UgdmEgYSBkaXNjdXRpciBzb2JyZSBsYSBpbnRlcnByZXRhY2nDs24gZGUgY29lZmljaWVudGVzIHkgbGEgZXZhbHVhY2nDs24gZW4gbGFzIHNpZ3VpZW50ZXMgc2VjY2lvbmVzLg0KDQojIyMgQ3JlYWNpw7NuIGRlIGbDs3JtdWxhcw0KDQpQYXJhIGNyZWFyIHZhcmlvcyBtb2RvZWxvcyBkZSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgcG9kZW1vcyB1dGlsaXphciBsYSBmdW5jacOzbiBgZm9ybXVsYXNgIGRlbCBwYXF1ZXRlICoqbW9kZWxyKiogcGFyYSBjcmVhciB1biBvYmpldG8gcXVlIGNvbnRpZW5lIHRvZGFzIGxhcyBmw7NybXVsYXMgcXVlIHZhbW9zIGEgdXRpbGl6YXIuIA0KDQpFbiBgLnJlc3BvbnNlYCBlc3BlY2lmaWNhbW9zIGxhIHZhcmlhYmxlIHJlc3B1ZXN0YSBkZSBudWVzdHJhcyBmw7NybXVsYXMgeSBsdWVnbyBub21icmFtb3MgbGFzIGbDs3JtdWxhcyBxdWUgcXVlcmFtb3MgYXJtYXIuDQoNCkFzw60sIGFybWFyZW1vcyBkaXN0aW50b3MgbW9kZWxvcyBjb21iaW5hbmRvIGRpc3RpbnRhcyB2YXJpYWJsZXMuIFNlIGdlbmVyYW4gNyBtb2RlbG9zIGRpc3RpbnRvcyBwYXJhIHByZWRlY2lyIGxhIHN1cGVydml2ZW5jaWEgZW4gZnVuY2nDs24gZGUgZGlzdGludGFzIGNvbWJpbmFjaW9uZXMgZGUgbGFzIHZhcmlhYmxlcyBwY2xhc3MsIHNleCwgYWdlIHkgZmFyZS4gDQoNCmBgYHtyfQ0KIyBDcmVhY2nDs24gZGUgZsOzcm11bGFzDQpsb2dpdF9mb3JtdWxhcyA8LSBmb3JtdWxhcygucmVzcG9uc2UgPSB+IFN1cnZpdmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3MgPSB+IFBjbGFzcywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzZXggPSB+IFNleCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBhZ2UgPSB+IEFnZSwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgUGNTID0gfiBQY2xhc3MgKyBTZXgsICMgbW9kZWxvIGNvbiBsYXMgdmFyaWFibGVzIHF1ZSBtw6FzIHBhcmVjZW4gZGl2aWRpciBlbCB0YXJnZXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFBjU0EgPSB+IFBjbGFzcyArIFNleCArIEFnZSwgIyBtb2RlbG8gZ2xtMQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgUGNTRiA9IH4gUGNsYXNzICsgU2V4ICsgRmFyZSwgICMgbW9kZWxvIHByZXZpbyBzaW4gQWdlDQogICAgICAgICAgICAgICAgICAgICAgICAgICBQY1NBRiA9IH4gUGNsYXNzICsgU2V4ICsgQWdlICsgRmFyZSAjIG1vZGVsbyBnbG0xIGNvbiBGYXJlICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICkNCmxvZ2l0X2Zvcm11bGFzICMgb2JzZXJ2YW1vcyBlbCBvYmpldG8gZm9ybXVsYXMNCmBgYA0KDQojIyMgQ3JlYWNpw7NuIGRlIG1vZGVsb3MNCg0KUHJvY2VkZW1vcyBhIGNyZWFyIGxvcyBtb2RlbG9zIGEgcGFydGlyIGRlIGVzdGFzIGbDs3JtdWxhcy4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQptb2RlbHMgPC0gZGF0YV9mcmFtZShsb2dpdF9mb3JtdWxhcykgJT4lICMgZGF0YWZyYW1lIGEgcGFydGlyIGRlbCBvYmpldG8gZm9ybXVsYXMNCiAgbXV0YXRlKG1vZGVscyA9IG5hbWVzKGxvZ2l0X2Zvcm11bGFzKSwgIyBjb2x1bW5hIGNvbiBsb3Mgbm9tYnJlcyBkZSBsYXMgZm9ybXVsYXMNCiAgICAgICAgIGV4cHJlc3Npb24gPSBwYXN0ZShsb2dpdF9mb3JtdWxhcyksICMgY29sdW1uYSBjb24gbGFzIGV4cHJlc2lvbmVzIGRlIGxhcyBmb3JtdWxhcw0KICAgICAgICAgbW9kID0gbWFwKGxvZ2l0X2Zvcm11bGFzLCB+Z2xtKC4sIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSBkZnRpdGFuaWNfdHJhaW4pKSkgIyBRdWUgZXN0YW1vcyBoYWNpZW5kbyBhY8OhPyBRdWUgdmFtb3MgYSBlbmNvbnRyYXIgZW4gbGEgY29sdW1uYT8NCm1vZGVscw0KYGBgDQoNCiMjIyBNb2RlbG9zIHNpbXBsZXMNCg0KUHJvYmFtb3MgbG9zIHByaW1lcm9zIHRyZXMgbW9kZWxvcywgYXF1ZWxsb3MgcXVlIHRpZW5lbiB1biDDum5pY28gcHJlZGljdG9yLiBVc2Ftb3MgbGEgZnVuY2nDs24gX3RpZHlfIHBhcmEgb2J0ZW5lciBsb3MgcGFyw6FtZXRyb3MgZXN0aW1hZG9zIHBhcmEgZXN0b3MgdHJlcyBtb2RlbG9zLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCm1vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHMgJWluJSBjKCdjbGFzcycsJ3NleCcsJ2FnZScpKSAlPiUNCiAgbXV0YXRlKHRpZHkgPSBtYXAobW9kLCB0aWR5KSkgICMgUXXDqSByZWFsaXphbW9zIGVuIGVzdGUgcGFzbz8gUXVlIHZhIGEgdGVuZXIgZXN0YSBjb2x1bW5hPw0KYGBgDQoNClBhcmEgYWNjZWRlciBhIGxvcyBlbGVtZW50b3MgZGUgbGEgbnVldmEgY29sdW1uYSBfdGlkeV8gZGViZW1vcyBkZXNhbmlkYXJsYSAodXNhbmRvIGB1bm5lc3QoKWApLiBFbCBhbmlkYWRvIGNyZWEgdW5hIGNvbHVtbmEgZGUgbGlzdGFzIGRlIGRhdGFmcmFtZXMsIGVzIGltcGzDrWNpdGFtZW50ZSB1bmEgb3BlcmFjacOzbiBkZSByZXN1bWVuOiBvYnRpZW5lIHVuYSBmaWxhIHBhcmEgY2FkYSBncnVwbyBkZWZpbmlkbyBwb3IgbGFzIGNvbHVtbmFzIG5vIGFuaWRhZGFzLiBEZXNhbmlkYXIgbG8gYXBsYW5hIGRlIG51ZXZvIGVuIGNvbHVtbmFzIHJlZ3VsYXJlcy4gUGFyYSBtYXlvciBkZXRhbGxlLCBzdWdlcmltb3MgdmVyIGxhIGRvY3VtZW50YWNpw7NuIGRlIHRpZHlyIHNvYnJlIFtOZXN0IGFuZCB1bm5lc3RdKGh0dHBzOi8vdGlkeXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbmVzdC5odG1sKS4gDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KbW9kZWxzICU+JSANCiAgZmlsdGVyKG1vZGVscyAlaW4lIGMoJ2NsYXNzJywnc2V4JywnYWdlJykpICU+JQ0KICBtdXRhdGUodGlkeSA9IG1hcChtb2QsIHRpZHkpKSAlPiUgICMgUXXDqSByZWFsaXphbW9zIGVuIGVzdGUgcGFzbz8gUXVlIHZhIGEgdGVuZXIgZXN0YSBjb2x1bW5hPw0KICB1bm5lc3QodGlkeSkgJT4lIA0KICBtdXRhdGUoZXN0aW1hdGU9cm91bmQoZXN0aW1hdGUsNSksICMgcmVkb25kZWFtb3MgdmFsb3JlcyBwYXJhIGZhY2lsaXRhciBsZWN0dXJhDQogICAgICAgICBwLnZhbHVlPXJvdW5kKHAudmFsdWUsNCkpDQpgYGANCg0KT2JzZXJ2YW1vcyBxdWUgdG9kb3MgbG9zIG1vZGVsb3MgdGllbmVuIGNvZWZpY2llbnRlcyBzaWduaWZpY2F0aXZvcyBhdW5xdWUgZWwgZGUgQWdlIHNlIGVuY3VlbnRyYSBtdXkgY2VyY2FubyBhIG51ZXN0cm8gdmFsb3IgZGUgcmVjaGF6by4NCg0KIyMgSW50ZXJwcmV0YWNpw7NuIGRlIGxvcyBjb2VmaWNpZW50ZXMNCg0KUmVjb3JkYW5kbyBsYSBlY3VhY2nDs24gcGFyYSBtb2RlbGFyIGxhIHByb2JhYmlsaWRhZDoNCg0KJFAoWT0xfFgpPSBcZnJhY3tlXntcYmV0YV8wICsgXHN1bVxsaW1pdHNfe2o9MX1ecCBcYmV0YV9qIFh9fXsxK2Vee1xiZXRhXzAgKyBcc3VtXGxpbWl0c197aj0xfV5wIFxiZXRhX2ogWH19JA0KDQpTZSBvYnNlcnZhIHF1ZSBhaG9yYSBsYXMgdmFyaWFibGVzIHlhIG5vIHRpZW5lbiB1bmEgcmVsYWNpw7NuIGxpbmVhbCBjb24gbGEgcHJvYmFiaWxpZGFkLiBFbiBlc3RlIG1vZGVsbyB1biBjb2VmaWNpZW50ZSBwb3NpdGl2byBpbmRpY2EgcXVlIGZyZW50ZSBhIGF1bWVudG9zIGRlIGRpY2hhIHZhcmlhYmxlIGxhIHByb2JhYmlsaWRhZCBhdW1lbnRhLCBtaWVudHJhcyBxdWUgdW4gY29lZmljaWVudGUgbmVnYXRpdm8gbm9zIGluZGljYSBsbyBjb250cmFyaW8uIFBhcmEgbnVlc3Ryb3MgbW9kZWxvczoNCg0KKipNb2RlbG8gY2xhc3MqKg0KDQoqIM6yMCA9IGByIHJvdW5kKChtb2RlbHMgJT4lIGZpbHRlcihtb2RlbHMgJWluJSBjKCdjbGFzcycpKSAlPiUgbXV0YXRlKHRpZHkgPSBtYXAobW9kLCB0aWR5KSkgJT4lICB1bm5lc3QodGlkeSkgJT4lIGZpbHRlcih0ZXJtID09ICcoSW50ZXJjZXB0KScpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgY29ycmVzcG9uZGUgYSBwZXJzb25hcyBxdWUgdmlhamFyb24gZW4gcHJpbWVyYSBjbGFzZS4NCg0KKiDOsjEgPSBgciByb3VuZCgobW9kZWxzICU+JSBmaWx0ZXIobW9kZWxzICVpbiUgYygnY2xhc3MnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnUGNsYXNzMicpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgeSDOsjIgPSBgciByb3VuZCgobW9kZWxzICU+JSBmaWx0ZXIobW9kZWxzICVpbiUgYygnY2xhc3MnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnUGNsYXNzMycpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgY29ycmVzcG9uZGVuIGEgcGVyc29uYXMgcXVlIHZpYWphcm9uIGVuIGxhIHNlZ3VuZGEgeSB0ZXJjZXJhIGNsYXNlLCByZXNwZWN0aXZhbWVudGUuIEVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGVuIGFtYm9zIGNhc29zIGZ1ZSBuZWdhdGl2bywgaW5kaWNhbmRvIHF1ZSBsYSAqKnByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIGRpc21pbnV5ZSoqIGVuIGNvbXBhcmFjacOzbiBjb24gbGEgcHJpbWVyYSBjbGFzZS4gIEFsIHNlciBtw6FzIG5lZ2F0aXZvIGVsIGNvZWZpY2llbnRlIGVzdGltYWRvIGRlIFBjbGFzczMsIHNlIGVzcGVyYSBxdWUgbGEgcHJvYmFiaWxpZGFkIGRlIFN1cGVydml2ZW5jaWEgc2VhIG1lbm9yIGHDum4gcGFyYSBlc3RhIGNsYXNlIHF1ZSBwYXJhIHVuIHBhc2FqZXJvIGVuIGNsYXNlIDIuDQoNCioqTW9kZWxvIHNleCoqDQoNCiogzrIwID0gYHIgcm91bmQoKG1vZGVscyAlPiUgZmlsdGVyKG1vZGVscyAlaW4lIGMoJ3NleCcpKSAlPiUgbXV0YXRlKHRpZHkgPSBtYXAobW9kLCB0aWR5KSkgJT4lICB1bm5lc3QodGlkeSkgJT4lIGZpbHRlcih0ZXJtID09ICcoSW50ZXJjZXB0KScpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgY29ycmVzcG9uZGUgYSBtdWplcmVzIHF1ZSB2aWFqYXJvbiBhIGJvcmRvIGRlbCB0aXRhbmljLg0KDQoqIM6yMSA9IGByIHJvdW5kKChtb2RlbHMgJT4lIGZpbHRlcihtb2RlbHMgJWluJSBjKCdzZXgnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnU2V4bWFsZScpICU+JSBzZWxlY3QoZXN0aW1hdGUpKSRlc3RpbWF0ZSw1KWAgcmVwcmVzZW50YSBhIGxvcyBob21icmVzIHF1ZSB2aWFqYXJvbiBlbiBlbCB0aXRhbmljIGUgaW5kaWNhIHF1ZSAqKmxhIHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIHNlIHJlZHVjZSoqIGVuIGNvbXBhcmFjacOzbiBhIGxhcyBtdWplcmVzLg0KDQoqKk1vZGVsbyBhZ2UqKg0KDQoqIM6yMSA9IGByIHJvdW5kKChtb2RlbHMgJT4lIGZpbHRlcihtb2RlbHMgJWluJSBjKCdhZ2UnKSkgJT4lIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCwgdGlkeSkpICU+JSAgdW5uZXN0KHRpZHkpICU+JSBmaWx0ZXIodGVybSA9PSAnQWdlJykgJT4lIHNlbGVjdChlc3RpbWF0ZSkpJGVzdGltYXRlLDUpYCBpbmRpY2EgcXVlIGxhICoqcHJvYmFiaWxpZGFkIGRlIHN1cGVydml2ZW5jaWEgZGlzbWludXllKiogcG9yIGNhZGEgYcOxbyBtw6FzIGRlIGVkYWQgZGUgbGEgcGVyc29uYS4NCg0KIyMjIMK/UXXDqSBwYXNhcsOtYSBlbiB1biBtb2RlbG8gbcO6bHRpcGxlPw0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCm1vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHMgJWluJSBjKCdQY1NBJykpICU+JQ0KICBtdXRhdGUodGlkeSA9IG1hcChtb2QsIHRpZHkpKSAlPiUgDQogIHVubmVzdCh0aWR5KSAlPiUgDQogIG11dGF0ZShlc3RpbWF0ZT1yb3VuZChlc3RpbWF0ZSw1KSwNCiAgICAgICAgIHAudmFsdWU9cm91bmQocC52YWx1ZSw0KSkNCmBgYA0KQXPDrSBjb21vIHNlIG9ic2VydsOzIGVuIGVsIG1vZGVsbyBzaW1wbGUgcGFyYSBsYSB2YXJpYWJsZSBQY2xhc3MsIGVuIGVzdGUgY2FzbyBsb3MgY29lZmljaWVudGVzIGVzdGltYWRvcyBkZSBQY2xhc3MyIHkgUGNsYXNzMyByZXN1bHRhbiBuZWdhdGl2b3MsIGluZGljYW5kbyBxdWUgc2kgZWwgcGFzYWplcm8gcGVydGVuZWNlIGEgc2VndW5kYSBvIHRlcmNlcmEgY2xhc2UsIHN1IHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmllbmNpYSBlc3BlcmFkYSBkaXNtaW51eWUgcmVzcGVjdG8gZGUgdW5hIHBlcnNvbmEgZGUgcHJpbWVyYSBjbGFzZSwgZGFkYXMgbGFzIGRlbcOhcyB2YXJpYWJsZXMgZGVsIG1vZGVsbyAoU2V4IHkgQWdlKS4gDQoNClBhcmEgbGEgdmFyaWFibGUgU2V4IG9ic2VydmFtb3MgcXVlIGVsIGNvZWZpY2llbnRlIFNleE1hbGUgcmVzdWx0YSBuZWdhdGl2bzsgZXN0byBzaWduaWZpY2EgcXVlIGxhIHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIGVzcGVyYWRhIGRlIGxvcyBob21icmVzIGRpc21pbnV5ZSByZXNwZWN0byBhIGxhIGRlIGxhcyBtdWplcmVzLCBkYWRhcyBsYXMgZGVtw6FzIHZhcmlhYmxlcyBkZWwgbW9kZWxvLg0KDQpQYXJhIGxhIHZhcmlhYmxlIEFnZSwgZWwgY29lZmljaWVudGUgZXN0aW1hZG8gdGFtYmnDqW4gcmVzdWx0YSBuZWdhdGl2bywgaW5kaWNhbmRvIHF1ZSBhbnRlIHVuIGF1bWVudG8gZW4gbGEgZWRhZCBkZWwgcGFzYWplcm8sIHN1IHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIGVzcGVyYWRhIGRpc21pbnV5ZSwgZGFkYXMgbGFzIGRlbcOhcyB2YXJpYWJsZXMuDQoNCkFzaW1pc21vLCBzZSBvYnNlcnZhIHF1ZSBsb3MgY29lZmljaWVudGVzIHJlc3VsdGFuIHRvZG9zIHNpZ25pZmljYXRpdm9zIGVuIGVzdGUgbW9kZWxvIChwLXZhbG9yPDAuMDUpLiANCg0KUmVjb21lbmRhbW9zIGxlZXIgZWwgW2NhcMOtdHVsbyBkZSByZWdyZXNpw7NuIGxvZ8Otc3RpY2FdKGh0dHBzOi8vY2hyaXN0b3BobS5naXRodWIuaW8vaW50ZXJwcmV0YWJsZS1tbC1ib29rL2xvZ2lzdGljLmh0bWwpIGRlICpJbnRlcnByZXRhYmxlIE1hY2hpbmUgTGVhcm5pbmc6IEEgR3VpZGUgZm9yIE1ha2luZyBCbGFjayBCb3ggTW9kZWxzIEV4cGxhaW5hYmxlKiBkZSBNb2xuYXIgQ3Jpc3RvcGggcGFyYSB1bmEgZGlzY3VzacOzbiBtw6FzIHByb2Z1bmRhIGRlIGxhIGludGVycHJldGFjacOzbiBkZSB1biBtb2RlbG8gZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhLg0KDQojIyBFdmFsdWFjacOzbiBkZSB0b2RvcyBsb3MgbW9kZWxvcw0KDQpDb24gYG1hcCgpYCBhZ3JlZ2Ftb3MgbGEgZnVuY2nDs24gYGdsYW5jZWAgcGFyYSB0cmFlciBpbmZvcm1hY2nDs24gcmVsZXZhbnRlIHBhcmEgbGEgZXZhbHVhY2nDs24gZGVsIG1vZGVsby4gQ29uIGB1bm5lc3QoKWAgYWNjZWRlbW9zIGEgZGljaGEgaW5mb3JtYWNpw7NuLiBQb3Igw7psdGltbywgYWdyZWdhbW9zIHVuYSBjb2x1bW5hIGNvbiBlbCBwb3JjZW50YWplIGRlIGRldmlhbmNlIGV4cGxpY2FkbyBwb3IgY2FkYSBtb2RlbG8geSBvcmRlbmFtb3MgZWwgZGF0YXNldCBzZWfDum4gc3UgdmFsb3IgZGUgZGV2aWFuY2UuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBDYWxjdWxhciBsYXMgbWVkaWRhcyBkZSBldmFsdWFjacOzbiBwYXJhIGNhZGEgbW9kZWxvDQptb2RlbHMgPC0gbW9kZWxzICU+JSANCiAgbXV0YXRlKGdsYW5jZSA9IG1hcChtb2QsZ2xhbmNlKSkNCiMgT2J0ZW5lciBsYXMgbWVkaWRhcyBkZSBldmFsdWFjaW9uIGRlIGludGVyZXMNCm1vZGVscyAlPiUgDQogIHVubmVzdChnbGFuY2UpICU+JQ0KICAjIENhbGN1bGFtb3MgbGEgZGV2aWFuY2UgZXhwbGljYWRhDQogIG11dGF0ZShwZXJjX2V4cGxhaW5lZF9kZXYgPSAxLWRldmlhbmNlL251bGwuZGV2aWFuY2UpICU+JSANCiAgc2VsZWN0KC1jKG1vZGVscywgZGYubnVsbCwgQUlDLCBCSUMpKSAlPiUgDQogIGFycmFuZ2UoZGV2aWFuY2UpDQpgYGANCg0KTG9zIG1vZGVsb3MgcXVlIGluY2x1eWVuIGxhcyAzIHZhcmlhYmxlcyBQY2xhc3MsIFNleCB5IEFnZSBwYXJlY2VuIHNlciBsb3MgcXVlIG1pbmltaXphbiBsYSBkZXZpYW5jZS4gT2JzZXJ2YW1vcyB0YW1iacOpbiBxdWUgbG9zIDIgw7psdGltb3MgbW9kZWxvcyByZWR1Y2VuIG11eSBwb2NvIGxhIGRldmlhbmNlIHJlc3BlY3RvIGEgbGEgZGV2aWFuY2UgbnVsYS4NCg0KIyMjIEdyw6FmaWNvcyBkZSBFdmFsdWFjacOzbg0KDQpSZWFsaXphbW9zIGxvcyBncsOhZmljb3MgcGFyYSBlbCBtb2RlbG8gY29tcGxldG8geSB1bm8gZGUgbG9zIG1vZGVsb3MgY29uIG1heW9yIGRldmlhbmNlIChBZ2UpLg0KDQpDb21lbnphbW9zIGFncmVnYW5kbyBsYXMgcHJlZGljY2lvbmVzIGNvbiBgYXVnbWVudGAgY29uIGVsIHBhcsOhbWV0cm8gYHR5cGU9InJlc3BvbnNlImAuIExhIGZ1bmNpw7NuIGF1Z21lbnQgaGVyZWRhIGVsIGFyZ3VtZW50byB0eXBlLnByZWRpY3QgZGUgbGEgZnVuY2nDs24gcHJlZGljdC4NCg0KICAqIFNpIGB0eXBlLnByZWRpY3QgPSAnbGluaydgIGxhIHByZWRpY2Npw7NuIGVzIGVuIHTDqXJtaW5vcyBkZSBsYSBmdW5jacOzbiBsaW5rLiBFbiBudWVzdHJvIGNhc28gc29uIGVsIGxvZ2FyaXRtbyBkZSBsYXMgb2RkcywgZXMgZGVjaXIsIGxvcyB2YWxvcmVzIHF1ZSB0b21hIGxhIGV4cHJlc2nDs24gbG9naXQuDQogIA0KICAqIFNpIGB0eXBlLnByZWRpY3QgPSAncmVzcG9uc2UnYCBsYSBwcmVkaWNjacOzbiBzb24gbGFzIHByb2JhYmlsaWRhZGVzIGRlIHF1ZSBsYSBvYnNlcnZhY2nDs24gcGVydGVuZXpjYSBhIGxhIGNsYXNlIHBvc2l0aXZhLiBFbiBudWVzdHJvIGNhc28sIGRldnVlbHZlIGxhIHByb2JhYmlsaWRhZCBkZSBsYSBxdWUgcGVyc29uYSBzb2JyZXZpdmEuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KIyBBw7FhZGlyIGxhcyBwcmVkaWNjaW9uZXMNCm1vZGVscyA8LSBtb2RlbHMgJT4lIA0KICBtdXRhdGUocHJlZD0gbWFwKG1vZCwgYXVnbWVudCwgdHlwZS5wcmVkaWN0ID0gInJlc3BvbnNlIikpDQojT2JzZXJ2YWNpb25lcyBjb24gcHJvYmFiaWxpZGFkIG3DoXMgYmFqYQ0KbW9kZWxzJHByZWQkUGNTQUYgJT4lIGFycmFuZ2UoLmZpdHRlZCkgJT4lIGhlYWQoMTApDQojT2JzZXJ2YWNpb25lcyBjb24gcHJvYmFiaWxpZGFkIG3DoXMgYWx0YQ0KbW9kZWxzJHByZWQkUGNTQUYgJT4lIGFycmFuZ2UoZGVzYyguZml0dGVkKSkgJT4lIGhlYWQoMTApDQpgYGANCg0KR3VhcmRhbW9zIGxhcyBwcmVkaWNjaW9uZXMgcGFyYSBsb3MgbW9kZWxvcyBtZW5jaW9uYWRvcy4NCg0KYGBge3J9DQojIE1vZGVsbyBjb21wbGV0bw0KcHJlZGljdGlvbl9mdWxsIDwtIG1vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHM9PSJQY1NBRiIpICU+JSANCiAgdW5uZXN0KHByZWQpDQojTW9kZWxvIG1hbG8NCnByZWRpY3Rpb25fYmFkIDwtIG1vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHM9PSJhZ2UiKSAlPiUgDQogIHVubmVzdChwcmVkKQ0KYGBgDQoNCg0KIyMjIyBWaW9saW4gcGxvdHMNCg0KYGBge3J9DQojIGdyYWZpY2Ftb3MgZWwgbW9kZWxvIGNvbXBsZXRvDQp2aW9saW5fZnVsbCA9IGdncGxvdChwcmVkaWN0aW9uX2Z1bGwsIGFlcyh4PVN1cnZpdmVkLCB5PS5maXR0ZWQsIGdyb3VwPVN1cnZpdmVkLCBmaWxsPWZhY3RvcihTdXJ2aXZlZCkpKSArIA0KICBnZW9tX3Zpb2xpbigpICsNCiAgdGhlbWVfYncoKSArDQogIGd1aWRlcyhzY2FsZT0ibm9uZSIpICsNCiAgbGFicyh0aXRsZT0nVmlvbGluIHBsb3QnLCBzdWJ0aXRsZT0nTW9kZWxvIGNvbXBsZXRvJywgeT0nUHJlZGljdGVkIHByb2JhYmlsaXR5JykNCiMgZ3JhZmljYW1vcyBlbCBtb2RlbG8gbWFsbw0KdmlvbGluX2JhZCA9IGdncGxvdChwcmVkaWN0aW9uX2JhZCwgYWVzKHg9U3Vydml2ZWQsIHk9LmZpdHRlZCwgZ3JvdXA9U3Vydml2ZWQsIGZpbGw9ZmFjdG9yKFN1cnZpdmVkKSkpICsgDQogIGdlb21fdmlvbGluKCkgKyANCiAgdGhlbWVfYncoKSArDQogIGd1aWRlcyhzY2FsZT0ibm9uZSIpICsNCiAgbGFicyh0aXRsZT0nVmlvbGluIHBsb3QnLCBzdWJ0aXRsZT0nTW9kZWxvIG1hbG8nLCB5PSdQcmVkaWN0ZWQgcHJvYmFiaWxpdHknKQ0KIyBtb3N0cmFtb3MgYW1ib3MNCnBsb3RfZ3JpZCh2aW9saW5fYmFkLCB2aW9saW5fZnVsbCkNCmBgYA0KDQpFbiBsb3MgZ3LDoWZpY29zIGRlIHZpb2xpbiBvYnNlcnZhbW9zOg0KDQogICogRW4gZWwgZWplIGRlIGFic2Npc2FzIGxhIGNsYXNlIHZlcmRhZGVyYTogU3Vydml2ZWQgbyBObyBTdXJ2aXZlZCAoMSBvIDApLg0KICANCiAgKiBFbiBlbCBlamUgZGUgb3JkZW5hZGFzIGxhIHByb2JhYmlsaWRhZCBwcmVkaWNoYSBwb3IgbnVlc3RybyBtb2RlbG8uDQogIA0KICAqIEVsIGdyw6FmaWNvIG5vcyBtdWVzdHJhIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGEgY2FudGlkYWQgZGUgb2JzZXJ2YWNpb25lcyBwb3Igc3UgY2xhc2UgcmVhbCB5IGxhIHByb2JhYmlsaWRhZCBxdWUgbGUgYXNpZ25hIG51ZXN0cm8gbW9kZWxvLg0KDQrCv0N1w6FsIHBhcmVjZSBzZXIgdW4gcHVudG8gZGUgY29ydGUgYWRlY3VhZG8gcGFyYSBjYWRhIG1vZGVsbz8NCg0KIyMjIyBHcsOhZmljbyBkZSBIb3NtZXItTGVtZXNob3cNCg0KU2UgZ2VuZXJhIHVuYSBmdW5jacOzbiBwYXJhIHJlYWxpemFyIHVuIGdyw6FmaWNvIGRlIEhvc21lci1MZW1lc2hvdyBwYXJhIHVuIGRhdGFzZXQuIFBhcmEgZWxsbyBzZSBmaWphbiBsb3Mgc2lndWllbnRlcyBwYXLDoW1ldHJvczogDQogIA0KKiBkYXRhc2V0OiBjb25qdW50byBkZSBkYXRvcw0KDQoqIHByZWRpY3RlZF9jb2x1bW46IGNvbHVtbmEgY29uIGxhIHByb2JhYmlsaWRhZCBwcmVkaWNoYQ0KDQoqIGNsYXNzX2NvbHVtbjogY29sdW1uYSBjb24gbGEgY2xhc2UgYSBwcmVkZWNpcg0KDQoqIHBvc3NpdGl2ZV92YWx1ZTogdmFsb3IgZGUgbGEgY2xhc2UgYSBwcmVkZWNpcg0KDQoqIGJpbnM6IGNhbnRpZGFkIGRlIGdydXBvcyBkZWwgZ3LDoWZpY28NCg0KKiBjb2xvcjogY29sb3IgZGUgbG9zIHB1bnRvcw0KDQoqIG51ZGdlX3g6IGRlc3BsYXphbWllbnRvIGRlIGxhIGV0aXF1ZXRhIGVuIGVsIGVqZSB4DQoNCiogbnVkZ2VfeTogZGVzcGxhemFtaWVudG8gZGUgbGEgZXRpcXVldGEgZW4gZWwgZWplIHkNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpIb3NtZXJfTGVtZXNob3dfcGxvdCA8LSBmdW5jdGlvbihkYXRhc2V0LCBwcmVkaWN0ZWRfY29sdW1uLCBjbGFzc19jb2x1bW4sIGJpbnMsIHBvc2l0aXZlX3ZhbHVlLCBjb2xvcj0nZm9yZXN0Z3JlZW4nLCBudWRnZV94PTAsIG51ZGdlX3k9MC4wNSl7DQogICMgQXNpZ25hciBsb3MgZ3J1cG9zIGEgbGFzIG9ic2VydmFjaW9uZXMgZGUgYWN1ZXJkbyBhIGxhIHByb2JhYmlsaWRhZCBwcmVkaWNoYQ0KICBkYXRhc2V0Wydncm91cCddIDwtIGJpbihkYXRhc2V0W3ByZWRpY3RlZF9jb2x1bW5dLCBuYmlucyA9IGJpbnMsIG1ldGhvZCA9ICdsJywgbGFiZWxzPWMoMTpiaW5zKSkNCiAgIyBDb250YXIgbGEgY2FudGlkYWQgZGUgY2Fzb3MgcG9zaXRpdm9zIHBvciBncnVwbw0KICBwb3NpdGl2ZV9jbGFzcyA8LSBkYXRhc2V0ICU+JSBmaWx0ZXIoISFzeW0oY2xhc3NfY29sdW1uKT09cG9zaXRpdmVfdmFsdWUpICU+JSBncm91cF9ieShncm91cCkgJT4lIGNvdW50KCkNCiAgIyBPYnRlbmVyIGxhIG1lZGlhIGRlIGxhcyBwcmVkaWNjaW9uZXMgcG9yIGdydXBvDQogIEhMX2RmIDwtIGRhdGFzZXQgJT4lIGdyb3VwX2J5KGdyb3VwKSAlPiUgc3VtbWFyaXNlKHByZWQ9bWVhbighIXN5bShwcmVkaWN0ZWRfY29sdW1uKSksIGNvdW50PW4oKSkgJT4lDQogICAgICAgICAgICBpbm5lcl9qb2luKC4scG9zaXRpdmVfY2xhc3MpICU+JQ0KICAgICAgICAgICAgbXV0YXRlKGZyZXE9bi9jb3VudCkNCiAgIyBHcsOhZmljbyANCiAgSE1fcGxvdCA8LSBnZ3Bsb3QoSExfZGYsIGFlcyh4PXByZWQsIHk9ZnJlcSkpICsgDQogICAgZ2VvbV9wb2ludChhZXMoc2l6ZT1uKSwgY29sb3I9Y29sb3IpICsNCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsPW4pLG51ZGdlX3kgPSBudWRnZV95KSsNCiAgICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGxpbmV0eXBlPSdkYXNoZWQnKSArIA0KICAgIHRoZW1lX2J3KCkgKw0KICAgIGxhYnModGl0bGU9J0hvc21lci1MZW1lc2hvdycsIHNpemU9J0Nhc29zJywgeD0iUHJvYmFiaWxpZGFkIFByZWRpY2hhIiwgeT0iRnJlY3VlbmNpYSBvYnNlcnZhZGEiKQ0KICByZXR1cm4oSE1fcGxvdCkNCn0NCmBgYA0KDQpHZW5lcmFtb3MgbG9zIGdyw6FmaWNvcyBwYXNhbmRvbGUgbG8gcGFyw6FtZXRyb3MuIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgbW9kZWxvIGNvbXBsZXRvDQpIb3NtZXJfTGVtZXNob3dfcGxvdChwcmVkaWN0aW9uX2Z1bGwsICcuZml0dGVkJywgJ1N1cnZpdmVkJywgMTAsIDEpICsNCiAgbGFicyhzdWJ0aXRsZT0iTW9kZWxvIGNvbXBsZXRvIikNCiMgbW9kZWxvIG1hbG8NCkhvc21lcl9MZW1lc2hvd19wbG90KHByZWRpY3Rpb25fYmFkLCAnLmZpdHRlZCcsICdTdXJ2aXZlZCcsIDEwLCAxLCBjb2xvciA9ICJmaXJlYnJpY2siKSArIGxhYnMoc3VidGl0bGU9Ik1vZGVsbyBtYWxvIikNCg0KYGBgDQoNCkVuIGxvcyAqKmdyw6FmaWNvcyBkZSBIb3NtZXItTGVtZXNob3cqKiBvYnNlcnZhbW9zOg0KDQogICogRW4gZWwgZWplIGRlIGFic2Npc2FzIGxhIHByb2JhYmlsaWRhZCBwcmVkaWNoYSBkZSBzdXBlcnZpdmVuY2lhLg0KICANCiAgKiBFbiBlbCBlamUgZGUgb3JkZW5hZGFzIGxhIGZyZWN1ZW5jaWEgZGUgY2xhc2UsIGVsIGNvY2llbnRlIGVudHJlIGNhbnRpZGFkIGRlIGluZGl2aWR1b3MgU3Vydml2ZWQgeSBlbCB0b3RhbCBkZSBpbmRpdmlkdW9zLg0KICANCiAgKiBMYSBsw61uZWEgcHVudGVhZGEgZGVzaWduYSBsYSBpZ3VhbGRhZCBlbnRyZSBwcm9iYWJpbGlkYWQgcHJlZGljaGEgeSBmcmVjdWVuY2lhIGRlIGNsYXNlLg0KICANCiAgKiBMb3MgY8OtcmN1bG9zLCBxdWUgc2UgY29uc3RydXllbiBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOg0KICAgICAgKiBTZSBkaXZpZGVuIGEgbGFzIG9ic2VydmFjaW9uZXMgZW4gYmlucyBlbiBiYXNlIGEgbGEgcHJvYmFiaWxpZGFkIHByZWRpY2hhDQogICAgICAqIFNlIGNhbGN1bGEgbGEgZnJlY3VlbmNpYSBkZSBjbGFzZSBwYXJhIGNhZGEgYmluDQogICAgICAqIEVuIGJhc2UgYSBlc3RhcyBkb3MgY29vcmRlbmFkYXMgc2UgdWJpY2EgYWwgY8OtcmN1bG8gZW4gZWwgZ3LDoWZpY28NCiAgICAgICogRWwgbsO6bWVybyB5IHRhbWHDsW8gaW5kaWNhbiBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIGVuIGRpY2hvIGdydXBvDQoNCkFxdWVsbG9zICoqY8OtcmN1bG9zIHF1ZSBzZSB1YmlxdWVuIHBvciBlbmNpbWEqKiBkZSBsYSBsw61uZWEgcHVudGVhZGEgaW5kaWNhbiBxdWUgZWwgKiptb2RlbG8gZXN0w6Egc3ViZXN0aW1hbmRvKiogbGEgcHJvYmFiaWxpZGFkIHBhcmEgZGljaG9zIGdydXBvcy4gTWllbnRyYXMgcXVlIHNpIGxvcyAqKmPDrXJjdWxvcyBzZSB1YmljYW4gcG9yIGRlYmFqbyoqIGVsIG1vZGVsbyBlc3TDoSAqKnNvYnJlZXN0aW1hbmRvKiogbGEgcHJvYmFiaWxpZGFkIHBhcmEgZGljaG9zIGdydXBvcy4NCg0Kwr9QYXJhIHF1w6kgdmFsb3JlcyBwYXJlY2UgZXhpc3RpciB1bmEgc29icmVlc3RpbWFjacOzbiBkZSBsYSBwcm9iYWJpbGlkYWQ/IMK/UGFyYSBjdcOhbGVzIHN1YmVzdGltYWNpw7NuPw0KDQojIyMjIEN1cnZhcyBST0MNCg0KYGBge3IsbWVzc2FnZT1GQUxTRX0NCiMgQ2FsY3VsYW1vcyBjdXJ2YXMgUk9DDQpyb2NfZnVsbCA8LSByb2MocmVzcG9uc2U9cHJlZGljdGlvbl9mdWxsJFN1cnZpdmVkLCBwcmVkaWN0b3I9cHJlZGljdGlvbl9mdWxsJC5maXR0ZWQpDQpyb2NfYmFkIDwtIHJvYyhyZXNwb25zZT1wcmVkaWN0aW9uX2JhZCRTdXJ2aXZlZCwgcHJlZGljdG9yPXByZWRpY3Rpb25fYmFkJC5maXR0ZWQpDQpgYGANCg0KR3JhZmljYW1vcyBhbWJhcyBlbiB1biBtaXNtbyBwbG90Lg0KDQpgYGB7cn0NCmdncm9jKGxpc3QoZnVsbD1yb2NfZnVsbCwgYmFkPXJvY19iYWQpLCBzaXplPTEpICsgDQogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9J2Rhc2hlZCcpICsNCiAgdGhlbWVfYncoKSArIA0KICBsYWJzKHRpdGxlPSdDdXJ2YXMgUk9DJywgY29sb3I9J01vZGVsbycpDQpwcmludChwYXN0ZSgnQVVDOiBNb2RlbG8gY29tcGxldG8nLCByb3VuZChyb2NfZnVsbCRhdWMsMykpKQ0KcHJpbnQocGFzdGUoJ0FVQzogTW9kZWxvIG1hbG8nLCByb3VuZChyb2NfYmFkJGF1YywzKSkpDQoNCmBgYA0KDQrCv1F1w6kgc2lnbmlmaWNhIGNhZGEgdW5vIGRlIGxvcyBlamVzPw0KDQojIyMgUHVudG8gZGUgY29ydGUNCg0KSGFzdGEgYWhvcmEgaGVtb3MgZXZhbHVhZG8gZWwgbW9kZWxvIGRlIG1hbmVyYSBnZW5lcmFsLCBwZXJvIGVsIHJlc3VsdGFkbyBmaW5hbCBkZWwgbW9kZWxvIGRlYmUgY29uc2lzdGlyIGVuIGFzaWduYXIgYSBsYSBwZXJzb25hIHVuYSBjbGFzZSBwcmVkaWNoYS4gRW4gbnVlc3RybyBjYXNvIGRlYmVtb3MgZXN0YWJsZWNlciB1biBwdW50byBkZSBjb3J0ZSBzZWfDum4gZWwgY3VhbCB2YW1vcyBhIHNlcGFyYXIgYSBsYXMgcGVyc29uYXMgZW4gcXVpZW5lcyBzb2JyZXZpdmVuIHkgcXVpZW5lcyBuby4NCg0KUHJvYmFtb3MgdmFyaW9zIHB1bnRvcyBkZSBjb3J0ZSB5IGdyYWZpY2Ftb3MgZWwgYWNjdXJhY3ksIGxhIHNlbnNpYmlsaWRhZCBvIHJlY2FsbCwgbGEgZXNwZWNpZmljaWRhZCB5IGxhIHByZWNpc2nDs24gcGFyYSBjYWRhIHVubyBkZSBlbGxvcy4NCg0KfCBDbGFzZXMgcHJlZGljaGFzIC8gQ2xhc2VzIHwgTmVnYXRpdmEgfCBQb3NpdGl2YSB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS18DQp8IE5lZ2F0aXZhICAgICAgICAgICAgICAgICB8IFRydWUgTmVnIHwgRmFsc2UgTmVnIHwNCnwgUG9zaXRpdmEgICAgICAgICAgICAgICAgIHwgRmFsc2UgUG9zIHwgVHJ1ZSBQb3MgfA0KDQpSZWNvcmRlbW9zIHF1ZToNCg0KJGFjY3VyYWN5ID0gXGZyYWN7VFArVE59e1RQK0ZQK0ZOK1ROfSQNCg0KJHNlbnNpdGl2aXR5ID0gcmVjYWxsID0gXGZyYWN7VFB9e1RQK0ZOfSQNCg0KJHNwZWNpZmljaXR5ID0gXGZyYWN7VE59e1ROK0ZQfSQNCg0KJHByZWNpc2lvbiA9IFxmcmFje1RQfXtUUCtGUH0kDQoNCmBgYHtyfQ0KcHJlZGljdGlvbl9tZXRyaWNzIDwtIGZ1bmN0aW9uKGN1dG9mZiwgcHJlZGljdGlvbnM9cHJlZGljdGlvbl9mdWxsKXsNCiAgdGFiIDwtIHByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUocHJlZGljdGVkX2NsYXNzID0gaWZfZWxzZSguZml0dGVkID4gY3V0b2ZmLCAxLCAwKSwNCiAgICAgICAgICAgU3Vydml2ZWQgPSBmYWN0b3IoU3Vydml2ZWQpKQ0KICBjb25mdXNpb25NYXRyaXgodGFibGUodGFiJHByZWRpY3RlZF9jbGFzcywgdGFiJFN1cnZpdmVkKSwgcG9zaXRpdmUgPSAiMSIpICU+JQ0KICAgIHRpZHkoKSAlPiUNCiAgICBzZWxlY3QodGVybSwgZXN0aW1hdGUpICU+JQ0KICAgIGZpbHRlcih0ZXJtICVpbiUgYygnYWNjdXJhY3knLCAnc2Vuc2l0aXZpdHknLCAnc3BlY2lmaWNpdHknLCAncHJlY2lzaW9uJykpICU+JQ0KICAgIG11dGF0ZShjdXRvZmYgPSBjdXRvZmYpDQp9DQpjdXRvZmZzID0gc2VxKDAuMDUsMC45NSwwLjAxKQ0KbG9naXRfcHJlZCA9IG1hcF9kZihjdXRvZmZzLCBwcmVkaWN0aW9uX21ldHJpY3MpICU+JSANCiAgbXV0YXRlKHRlcm0gPSBhcy5mYWN0b3IodGVybSksIGVzdGltYXRlID0gcm91bmQoZXN0aW1hdGUsIDMpKQ0KZ2dwbG90KGxvZ2l0X3ByZWQsIGFlcyhjdXRvZmYsZXN0aW1hdGUsIGdyb3VwPXRlcm0sIGNvbG9yPXRlcm0pKSArIGdlb21fbGluZShzaXplPTEpICsNCiAgdGhlbWVfYncoKSArDQogIGxhYnModGl0bGU9ICdBY2N1cmFjeSwgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5IHkgUHJlY2lzaW9uJywgc3VidGl0bGU9ICdNb2RlbG8gY29tcGxldG8nLCBjb2xvcj0iIikNCmBgYA0KDQrCv1F1w6kgcG9kZW1vcyBvYnNlcnZhciBlbiBlbCBncsOhZmljbz8NCg0Kwr9Qb2RlbW9zIGRlZmluaXIgdW4gYnVlbiBwdW50byBkZSBjb3J0ZT8gwr9DdcOhbCBzZXLDrWE/DQoNCsK/UG9yIHF1w6kgbGEgZXNwZWNpZmljaWRhZCB0aWVuZSBlc2UgY29tcG9ydGFtaWVudG8/DQoNCiMjIyBEYXRhc2V0IGRlIHRlc3RpbmcNCg0KU2VsZWNjaW9uYW1vcyBlbCBtb2RlbG8gY29tcGxldG8sIHlhIHF1ZSBlcyBlbCBxdWUgbWF4aW1pemFiYSBlbCBwb3JjZW50YWplIGRlIGRldmlhbmNlIGV4cGxpY2FkYSB5IGVuIGJhc2UgYSBsbyBxdWUgdmltb3MgZGVmaW5pbW9zIHVuIHB1bnRvIGRlIGNvcnRlIGVuIDAuNCAocHVlZGVuIHByb2JhciBvdHJvcyksIGRvbmRlIHNlIGNydXphbiBzZW5zaXRpdml0eSB5IHNwZWNpZmljaXR5Lg0KDQpDYWxjdWxhbW9zIGxhIG1hdHJpeiBkZSBjb25mdXNpw7NuIHBhcmEgbG9zIGRhdGFzZXRzIGRlIHRyYWluIHkgdGVzdC4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRX0NCnNlbF9jdXRvZmYgPSAwLjQNCiMgQ3JlYW1vcyBlbCBtb2RlbG8NCmZ1bGxfbW9kZWwgPC0gZ2xtKGxvZ2l0X2Zvcm11bGFzJFBjU0FGLCBmYW1pbHkgPSAnYmlub21pYWwnLCBkYXRhID0gZGZ0aXRhbmljX3RyYWluKQ0KIyBjYWxjdWxhbW9zIGxhcyBwcmVkaWNjaW9uZXMgc29icmUgZWwgZGF0YXNldCBkZSB0cmFpbg0KdGFibGVfdHJhaW4gPSBhdWdtZW50KHggPSBmdWxsX21vZGVsLCB0eXBlLnByZWRpY3Q9J3Jlc3BvbnNlJykNCiMgQ2xhc2lmaWNhbW9zIHV0aWxpemFtb3MgZWwgcHVudG8gZGUgY29ydGUNCnRhYmxlX3RyYWluID0gdGFibGVfdHJhaW4gJT4lIA0KICBtdXRhdGUocHJlZGljdGVkX2NsYXNzID0gaWZfZWxzZSguZml0dGVkPnNlbF9jdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwgDQogICAgICAgICBTdXJ2aXZlZCA9IGZhY3RvcihTdXJ2aXZlZCkpDQojIENyZWFtb3MgbGEgbWF0cml6IGRlIGNvbmZ1c2nDs24NCmNvbmZ1c2lvbk1hdHJpeCh0YWJsZSh0YWJsZV90cmFpbiRwcmVkaWN0ZWRfY2xhc3MsIHRhYmxlX3RyYWluJFN1cnZpdmVkKSwgcG9zaXRpdmUgPSAiMSIpDQpgYGANCg0KDQpgYGB7cixtZXNzYWdlPUZBTFNFfQ0KIyBBZ3JlZ2Ftb3MgbGEgcHJlZGljY2lvbmVzIGFsIGRhdGFzZXQgZGUgdGVzdGVvDQp0YWJsZV90ZXN0ID0gYXVnbWVudCh4ID0gZnVsbF9tb2RlbCwgbmV3ZGF0YT1kZnRpdGFuaWNfdGVzdCwgdHlwZS5wcmVkaWN0PSdyZXNwb25zZScpIA0KIyBDbGFzaWZpY2Ftb3MgdXRpbGl6YW1vcyBlbCBwdW50byBkZSBjb3J0ZQ0KdGFibGVfdGVzdCA9IHRhYmxlX3Rlc3QgJT4lIA0KICBtdXRhdGUocHJlZGljdGVkX2NsYXNzID0gaWZfZWxzZSguZml0dGVkPnNlbF9jdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwgDQogICAgICAgICBTdXJ2aXZlZCA9IGZhY3RvcihTdXJ2aXZlZCkpDQojIENyZWFtb3MgbGEgbWF0cml6IGRlIGNvbmZ1c2nDs24NCmNvbmZ1c2lvbk1hdHJpeCh0YWJsZSh0YWJsZV90ZXN0JHByZWRpY3RlZF9jbGFzcywgdGFibGVfdGVzdCRTdXJ2aXZlZCksIHBvc2l0aXZlID0gIjEiKQ0KYGBgDQoNCkFsIGV4aXN0aXIgZGVzYmFsYW5jZW8gZGUgY2xhc2VzLCBlcyBwb3NpYmxlIHF1ZSBlbCBhbmFsaXN0YSBkZXNlZSBzYWJlciBzaSBsYSBwcmVjaXNpw7NuIGdlbmVyYWwgKEFjY3VyYWN5KSBkZSBzdSBtb2RlbG8gZXMgbWVqb3IgcXVlIGxhIHByb3BvcmNpw7NuIGRlIGRhdG9zIGNvbiBsYSBjbGFzZSBtYXlvcml0YXJpYSAoTm8taW5mb3JtYXRpb24gUmF0ZSkuIGNvbmZ1c2lvbk1hdHJpeCB1c2EgbGEgZnVuY2nDs24gYmlub20udGVzdCBwYXJhIHByb2JhciBxdWUgbGEgcHJlY2lzacOzbiAoQWNjKSBlcyBtZWpvciBxdWUgbGEgdGFzYSBzaW4gaW5mb3JtYWNpw7NuIChOSVIpLiBTaSBlbCBQLVZhbHVlIFtBY2MgPiBOSVJdIHJlc3VsdGEgc2lnbmlmaWNhdGl2bywgZW50b25jZXMgcG9kZW1vcyBkZWNpciBxdWUgZXhpc3RlIGV2aWRlbmNpYSBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhIHBhcmEgZGVjaXIgcXVlIGxhIHByZWNpc2nDs24gZGVsIG1vZGVsbyBlcyBtZWpvciBxdWUgbGEgdGFzYSBzaW4gaW5mb3JtYWNpw7NuLiANCg0KIyMgRGVzYmFsYW5jZW8gZGUgbGEgY2xhc2UNCg0KQWwgZXhwbG9yYXIgZWwgZGF0YXNldCB2aW1vcyBxdWUgZXhpc3TDrWEgY2llcnRvIGRlc2JhbGFuY2UgZGUgY2xhc2UuIEVzdG8gcHVlZGUgdGVuZXIgdW4gZWZlY3RvIGVuIGxhcyBlc3RpbWFjaW9uZXMgZGVsIG1vZGVsbyB5IHN1IGNsYXNpZmljYWNpw7NuIGZpbmFsLg0KDQpFeGlzdGVuIGRvcyBtYW5lcmFzIHNlbmNpbGxhcyBjb24gbGFzIGN1YWxlcyBwb2RlbW9zIHRyYWJhamFyIGNvbiB1bmEgY2xhc2UgZGVzYmFsYW5jZWFkYToNCg0KICAqIFNvYnJlLW11ZXN0cmVvIChvdmVyc2FtcGxpbmcpIGRlIGxhIGNsYXNlIG1pbm9yaXRhcmlhDQogIA0KICAqIFN1Yi1tdWVzdHJlbyAodW5kZXJzYW1wbGluZykgZGUgbGEgY2xhc2UgbWF5b3JpdGFyaWENCiAgDQpMYSBmdW5jacOzbiBgZ2xtYCBwdWVkZSB0b21hciBjb21vIGFyZ3VtZW50byB1bmEgY29sdW1uYSAoYHdlaWd0aHNgKSBkZSBwb25kZXJhZG9yZXMgcGFyYSBwb2RlciBoYWNlciBlc3RvLiBQb2RlbW9zIGFzaWduYXIgcGVzb3MgbWF5b3JlcyBhIDEgYSBsYSBjbGFzZSBtaW5vcml0YXJpYSAob3ZlcnNhbXBsaW5nKSBvIG1lbm9yZXMgYSAxIGEgbGEgY2xhc2UgbWF5b3JpdGFyaWEgKHVuZGVyc2FtcGxpbmcpLiBFbiBudWVzdHJvIHByb2JsZW1hIHZhbW9zIGEgcmVhbGl6YXIgdW4gc29icmVzYW1wbGVvIGRlIGxhIGNsYXNlIG1pbm9yaXRhcmlhLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCiMgQ3JlYW1vcyBsYSBjb2x1bW5hIGRlIHBvbmRlcmFkb3Jlcw0KdHJhaW4gPC0gZGZ0aXRhbmljX3RyYWluICU+JSBtdXRhdGUod3QgPSBpZl9lbHNlKFN1cnZpdmVkID09IDEsIDEuMSwgMSkpDQojIENyZWFtb3MgbG9zIG1vZGVsb3MgY29uIGxhIGRhdGEgJ2JhbGFuY2VhZGEnDQpiYWxhbmNlZF9tb2RlbHMgPC0gZGF0YV9mcmFtZShsb2dpdF9mb3JtdWxhcykgJT4lICMgZGF0YWZyYW1lIGEgcGFydGlyIGRlbCBvYmpldG8gZm9ybXVsYXMNCiAgbXV0YXRlKG1vZGVscyA9IG5hbWVzKGxvZ2l0X2Zvcm11bGFzKSwgIyBjb2x1bW5hIGNvbiBsb3Mgbm9tYnJlcyBkZSBsYXMgZm9ybXVsYXMNCiAgICAgICAgIGV4cHJlc3Npb24gPSBwYXN0ZShsb2dpdF9mb3JtdWxhcyksICMgY29sdW1uYSBjb24gbGFzIGV4cHJlc2lvbmVzIGRlIGxhcyBmb3JtdWxhcw0KICAgICAgICAgbW9kID0gbWFwKGxvZ2l0X2Zvcm11bGFzLCB+Z2xtKC4sIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSB0cmFpbiwgd2VpZ2h0cyA9IHd0KSkpICNQYXNhbW9zIGxhIGNvbHVtbmEgd3QgY29tbyBwb25kZXJhZG9yZXMNCmBgYA0KDQpWZW1vcyBsYXMgZXN0aW1hY2lvbmVzIGRlIGxvcyBwYXLDoW1ldHJvcyBwYXJhIGVsIG1vZGVsbyBjb21wbGV0by4gwr9FeGlzdGVuIGNhbWJpb3M/DQoNCmBgYHtyLCAgd2FybmluZz1GQUxTRSwgZWNobz1GQUxTRX0NCmJhbGFuY2VkX21vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHMgPT0gIlBjU0FGIikgJT4lDQogIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCx0aWR5KSkgJT4lDQogIHVubmVzdCh0aWR5LCAuZHJvcCA9IFRSVUUpICU+JSANCiAgbXV0YXRlKGVzdGltYXRlPXJvdW5kKGVzdGltYXRlLDUpLA0KICAgICAgICAgcC52YWx1ZT1yb3VuZChwLnZhbHVlLDQpKQ0KYGBgDQoNCkFob3JhIHZlYW1vcyBsYSBldmFsdWFjacOzbiBkZSBsb3MgbW9kZWxvcyDCv1F1w6kgcGFzw7MgY29uIGVsIHBvcmNlbnRhamUgZGUgZGV2aWFuY2UgZXhwbGljYWRhPyDCv1kgY29uIGxhIG51bGE/DQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KYmFsYW5jZWRfbW9kZWxzIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgDQogIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kLGdsYW5jZSkpDQpiYWxhbmNlZF9tb2RlbHMgJT4lIA0KICB1bm5lc3QoZ2xhbmNlLCAuZHJvcCA9IFRSVUUpICU+JQ0KICBtdXRhdGUocGVyY19leHBsYWluZWRfZGV2ID0gMS1kZXZpYW5jZS9udWxsLmRldmlhbmNlKSAlPiUgDQogIHNlbGVjdCgtYyhtb2RlbHMsIGRmLm51bGwsIEFJQywgQklDKSkgJT4lIA0KICBhcnJhbmdlKGRldmlhbmNlKQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KYmFsYW5jZWRfbW9kZWxzIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgDQogIG11dGF0ZShwcmVkPSBtYXAobW9kLGF1Z21lbnQsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKQ0KcHJlZGljdGlvbl9mdWxsIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgDQogIGZpbHRlcihtb2RlbHM9PSJQY1NBRiIpICU+JSANCiAgdW5uZXN0KHByZWQsIC5kcm9wPVRSVUUpDQpyb2NfZnVsbCA8LSByb2MocmVzcG9uc2U9cHJlZGljdGlvbl9mdWxsJFN1cnZpdmVkLCBwcmVkaWN0b3I9cHJlZGljdGlvbl9mdWxsJC5maXR0ZWQpDQpwcmVkaWN0aW9uX2JhZCA8LSBiYWxhbmNlZF9tb2RlbHMgJT4lIA0KICBmaWx0ZXIobW9kZWxzPT0iYWdlIikgJT4lIA0KICB1bm5lc3QocHJlZCwgLmRyb3A9VFJVRSkNCnJvY19iYWQgPC0gcm9jKHJlc3BvbnNlPXByZWRpY3Rpb25fYmFkJFN1cnZpdmVkLCBwcmVkaWN0b3I9cHJlZGljdGlvbl9iYWQkLmZpdHRlZCkNCmBgYA0KDQojIyMgVmlvbGluIHBsb3RzLCBDdXJ2YXMgUk9DIHkgQVVDcw0KDQpSZWFsaXphbW9zIGxvcyBncsOhZmljb3MgZGUgdmlvbGluLCBsYXMgY3VydmFzIFJPQyB5IGNhbGN1bGFtb3MgbGFzIEFVQy4NCg0KYGBge3IsIHdhcm5pbmc9RiwgZWNobz1GQUxTRX0NCnZpb2xpbl9mdWxsID0gZ2dwbG90KHByZWRpY3Rpb25fZnVsbCwgYWVzKHggPSBTdXJ2aXZlZCwgeSA9LmZpdHRlZCwgZ3JvdXAgPSBTdXJ2aXZlZCwgZmlsbCA9IGZhY3RvcihTdXJ2aXZlZCkpKSArIA0KICBnZW9tX3Zpb2xpbigpICsNCiAgdGhlbWVfYncoKSArDQogIGd1aWRlcyhmaWxsPUZBTFNFKSArDQogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyBjb21wbGV0bycsIHk9J1ByZWRpY3RlZCBwcm9iYWJpbGl0eScpDQoNCnZpb2xpbl9iYWQ9Z2dwbG90KHByZWRpY3Rpb25fYmFkLCBhZXMoeCA9IFN1cnZpdmVkLCB5ID0uZml0dGVkLCBncm91cCA9IFN1cnZpdmVkLCBmaWxsID0gZmFjdG9yKFN1cnZpdmVkKSkpICsgDQogIGdlb21fdmlvbGluKCkgKyANCiAgdGhlbWVfYncoKSArDQogIGd1aWRlcyhmaWxsPUZBTFNFKSArDQogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyBtYWxvJywgeT0nUHJlZGljdGVkIHByb2JhYmlsaXR5JykNCg0KcGxvdF9ncmlkKHZpb2xpbl9iYWQsIHZpb2xpbl9mdWxsKQ0KDQpnZ3JvYyhsaXN0KGZ1bGw9cm9jX2Z1bGwsIGJhZD1yb2NfYmFkKSwgc2l6ZT0xKSArIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9J2Rhc2hlZCcpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGU9J0N1cnZhcyBST0MnLCBjb2xvcj0nTW9kZWxvJykNCg0KcHJpbnQocGFzdGUoJ0FVQyBNb2RlbG8gY29tcGxldG86Jywgcm91bmQocm9jX2Z1bGwkYXVjLDMpKSkNCg0KcHJpbnQocGFzdGUoJ0FVQyBNb2RlbG8gbWFsbzonLCByb3VuZChyb2NfYmFkJGF1YywzKSkpDQoNCmBgYA0KDQo+IMK/RMOzbmRlIHNlIHZlbiBsb3MgY2FtYmlvcyBtw6FzIG5vdG9yaW9zIHJlc3BlY3RvIGEgbnVlc3Ryb3MgbW9kZWxvcyBhbnRlcmlvcmVzIHF1ZSBubyB0ZW7DrWFuIGVuIGN1ZW50YSBlbCBkZXNiYWxhbmNlIGRlIGxhIGNsYXNlPw0KDQojIyMgUHVudG8gZGUgY29ydGUNCg0KVm9sdmVtb3MgYSByZWFsaXphciBsYXMgcHJ1ZWJhcyBwYXJhIHZhcmlvcyBwdW50b3MgZGUgY29ydGUgeSBncmFmaWNhbW9zIGVsIGFjY3VyYWN5LCBsYSBzZW5zaWJpbGlkYWQsIGxhIGVzcGVjaWZpY2lkYWQsIGVsIHJlY2FsbCB5IGxhIHByZWNpc2lvbiBwYXJhIGNhZGEgdW5vIGRlIGVsbG9zLg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmN1dG9mZnMgPSBzZXEoMC4wNSwwLjk1LDAuMDEpDQpsb2dpdF9wcmVkPSBtYXBfZGZyKGN1dG9mZnMsIHByZWRpY3Rpb25fbWV0cmljcyklPiUgbXV0YXRlKHRlcm09YXMuZmFjdG9yKHRlcm0pKQ0KDQpnZ3Bsb3QobG9naXRfcHJlZCwgYWVzKGN1dG9mZixlc3RpbWF0ZSwgZ3JvdXA9dGVybSwgY29sb3I9dGVybSkpICsgZ2VvbV9saW5lKHNpemU9MSkgKw0KICB0aGVtZV9idygpICsNCiAgbGFicyh0aXRsZT0gJ0FjY3VyYWN5LCBQcmVjaXNpb24sIFNlbnNpdGl2aXR5IHkgU3BlY2lmaWNpdHknLCBzdWJ0aXRsZT0gJ01vZGVsbyBjb21wbGV0bycsIGNvbG9yPSIiKQ0KYGBgDQoNCsK/UXXDqSBjYW1iaW9zIHZlbW9zIHJlc3BlY3RvIGFsIGdyw6FmaWNvIGFudGVyaW9yPw0KDQojIyMgRGF0YXNldCBkZSB0ZXN0aW5nDQoNClByb2JhbW9zIGVuIGVsIGRhdGFzZXQgZGUgdGVzdGluZyBudWVzdHJvIG1vZGVsbyBiYWxhbmNlYWRvLiBObyBlcyBuZWNlc2FyaW8gcXVlIGxlIGNyZWVtb3MgcGVzb3MgYWwgZGF0YXNldCBkZSB0ZXN0ZW8uDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KZnVsbF9tb2RlbCA8LSBnbG0obG9naXRfZm9ybXVsYXMkUGNTQUYsIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSB0cmFpbiwgd2VpZ2h0cyA9IHd0KQ0KDQp0YWJsZT0gYXVnbWVudCh4PWZ1bGxfbW9kZWwsIG5ld2RhdGE9ZGZ0aXRhbmljX3Rlc3QsIHR5cGUucHJlZGljdD0ncmVzcG9uc2UnKSANCg0KdGFibGU9dGFibGUgJT4lIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPjAuNDIsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwNCiAgICAgICAgICAgU3Vydml2ZWQgPSBmYWN0b3IoU3Vydml2ZWQpKQ0KDQpjb25mdXNpb25NYXRyaXgodGFibGUodGFibGUkcHJlZGljdGVkX2NsYXNzLCB0YWJsZSRTdXJ2aXZlZCksIHBvc2l0aXZlID0gIjEiKQ0KYGBgDQoNCg0K